本文主要是介绍Android网络资源下载时断点续传,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
转自http://blog.csdn.net/sodino/article/details/6535278
断点续传用到的知识点:
1.使用RandomAccessFile设定文件大小并于指定位置开始读数据[randomAccessFile.seek(position)]。
2.请求资源链接时指定所请求数据的返回范围。
httpURLConnection.setRequestProperty("Range", "bytes=" + start + "-" + (contentLength - 1));
效果图如下[CSDN]:
(相当抱歉,这个动画的时间太长了)
以下代码中的NetworkTool为通过个人编程经验封装好的网络工具类,强力推荐,当然也欢迎拍砖。
使用NetworkTool访问一个网络链接并获取数据的小示例为:
- HttpURLConnection httpConn = NetworkTool.openUrl(context, url);
- int respondCode = NetworkTool.connect(httpConn);
- if (respondCode == HttpURLConnection.HTTP_OK) {
- byte[] data = NetworkTool.fetchData_doClose(httpConn);
- String content = new String(data);
- data = null;
- // parse content
- } else {
- // handles something
- }
- NetworkTool.disconnect(httpConn);
代码中的DontPressWithParentButton可用于ListView中,当点击该Button时不会触发ListView中的OnItemClickListener。实现方法为重写Button的setPressed(boolean)方法:
- @Override
- public void setPressed(boolean pressed) {
- if (pressed && ((View) getParent()).isPressed()) {
- return;
- }
- super.setPressed(pressed);
- }
代码如下:
lab.sodino.downloadbreak.ActDownload.java
- package lab.sodino.downloadbreak;
- import java.io.File;
- import java.text.DecimalFormat;
- import lab.sodino.downloadbreak.bean.BeanDownload;
- import lab.sodino.downloadbreak.util.LogOut;
- import lab.sodino.downloadbreak.util.NetworkTool;
- import android.app.Activity;
- import android.content.Intent;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.view.View;
- import android.widget.Button;
- import android.widget.ProgressBar;
- import android.widget.TextView;
- public class ActDownload extends Activity {
- /** 下载存放地:"/sdcard/sodino/"。 */
- public static final String RES_LOAD_FOLDER = File.separator + "sdcard" + File.separator
- + "sodino" + File.separator;
- /** 刷新进度。 */
- public static final int REFRESH = 1;
- public static final int CODE = 10;
- private BeanDownload bean;
- private TextView txtName;
- private TextView txtProgress;
- private TextView txtSize;
- private ProgressBar progressBar;
- private Button btnAction;
- private Handler handler;
- private BtnListener btnListener;
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.l_download);
- initBeanDownload();
- initViews$Handler();
- }
- private void initBeanDownload() {
- bean = new BeanDownload();
- bean.name = "微信.apk";
- // 请找个可以无需跳转直接下载的地址
- bean.url = "http://XXOO.com/weixin20android16.apk";
- bean.state = BeanDownload.STATE_INTERRUPTED;
- bean.size = bean.loadedSize = 0l;
- bean.enable = true;
- }
- private void initViews$Handler() {
- txtName = (TextView) findViewById(R.id.txtName);
- txtName.setText(bean.name);
- txtProgress = (TextView) findViewById(R.id.txtProgress);
- txtProgress.setText(getProgressTxt(bean));
- txtSize = (TextView) findViewById(R.id.txtSize);
- txtSize.setText(formatSizeTxt(bean.size));
- progressBar = (ProgressBar) findViewById(R.id.progressBar);
- progressBar.setProgress(getProgressInt(bean, progressBar.getMax()));
- btnListener = new BtnListener();
- btnAction = (Button) findViewById(R.id.btnAction);
- btnAction.setOnClickListener(btnListener);
- btnAction.setText(getTxt(bean));
- btnAction.setEnabled(isEnable(bean));
- // handler
- handler = new Handler() {
- public void handleMessage(Message msg) {
- txtProgress.setText(getProgressTxt(bean));
- txtSize.setText(formatSizeTxt(bean.size));
- progressBar.setProgress(getProgressInt(bean, progressBar.getMax()));
- btnAction.setText(getTxt(bean));
- btnAction.setEnabled(isEnable(bean));
- }
- };
- }
- private void pauseDownload() {
- bean.enable = false;
- handler.sendEmptyMessage(REFRESH);
- }
- private void doDownload() {
- handler.sendEmptyMessage(REFRESH);
- new DownloadThread().start();
- }
- private void reloadDownload() {
- bean.size = bean.loadedSize = 0;
- bean.enable = true;
- doDownload();
- }
- private void installDownload() {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- String filePath = RES_LOAD_FOLDER + bean.name;
- intent.setDataAndType(Uri.parse("file://" + filePath),
- "application/vnd.android.package-archive");
- // 如果仅是简单的startActivity(intent),会造成onCreate()再执行一次。
- ActDownload.this.startActivityForResult(intent, CODE);
- }
- class BtnListener implements Button.OnClickListener {
- public void onClick(View v) {
- LogOut.out(this, "state:" + bean.state);
- switch (bean.state) {
- case BeanDownload.STATE_LOADING:
- // 点击了"暂停"
- pauseDownload();
- break;
- case BeanDownload.STATE_INTERRUPTED:
- // 点击了"继续"
- doDownload();
- break;
- case BeanDownload.STATE_DOWNLOAD_FAIL:
- // 点击了"重载"
- reloadDownload();
- break;
- case BeanDownload.STATE_COMPLETED:
- // 点击了"安装"
- installDownload();
- break;
- }
- }
- }
- class DownloadThread extends Thread {
- public void run() {
- bean.state = BeanDownload.STATE_LOADING;
- bean.enable = true;
- NetworkTool.download2File(ActDownload.this, bean, handler);
- LogOut.out(this, "size:" + bean.size + " loaded:" + bean.loadedSize + " enable:"
- + bean.enable);
- // 测试“重载”请释放下面代码的注释然后等待下载正常结束
- // bean.loadedSize = 0;
- if (bean.size > 0 && bean.loadedSize == bean.size) {
- String localPath = RES_LOAD_FOLDER + bean.name;
- File tmpFile = new File(localPath + ".tmp");
- tmpFile.renameTo(new File(localPath));
- bean.enable = false;
- bean.state = BeanDownload.STATE_COMPLETED;
- } else {
- if (bean.enable == false) {
- bean.state = BeanDownload.STATE_INTERRUPTED;
- } else {
- bean.state = BeanDownload.STATE_DOWNLOAD_FAIL;
- }
- }
- LogOut.out(this, "state=" + bean.state);
- handler.sendEmptyMessage(REFRESH);
- }
- }
- public static String getProgressTxt(BeanDownload bean) {
- String resStr = "0%";
- if (bean.size != 0) {
- double result = bean.loadedSize * 1.0 / bean.size;
- DecimalFormat decFormat = new DecimalFormat("#.#%");
- resStr = decFormat.format(result);
- }
- return resStr;
- }
- private String formatSizeTxt(long size) {
- String sizeTxt = "未知";
- if (size > 0) {
- size = size >> 10;
- sizeTxt = String.valueOf(size) + "k";
- }
- return sizeTxt;
- }
- public static int getProgressInt(BeanDownload bean, int max) {
- int result = (bean.size > 0) ? (int) (bean.loadedSize * max * 1.0 / bean.size) : 0;
- return result;
- }
- private String getTxt(BeanDownload bean) {
- String txt = "安装";
- switch (bean.state) {
- case BeanDownload.STATE_COMPLETED:
- txt = "安装";
- break;
- case BeanDownload.STATE_LOADING:
- txt = "暂停";
- break;
- case BeanDownload.STATE_INTERRUPTED:
- txt = "继续";
- break;
- case BeanDownload.STATE_DOWNLOAD_FAIL:
- txt = "重载";
- break;
- }
- return txt;
- }
- private boolean isEnable(BeanDownload bean) {
- boolean enable = true;
- if (bean.enable == false && bean.state == BeanDownload.STATE_LOADING) {
- enable = false;
- }
- return enable;
- }
- }
lab.sodino.downloadbreak.bean.BeanDownload.java
- package lab.sodino.downloadbreak.bean;
- /**
- * @author Sodino E-mail:sodinoopen@hotmail.com
- * @version Time:2011-6-8 下午11:33:10
- */
- public class BeanDownload {
- /** 正在下载数据。Button应显示“暂停”。 */
- public static final int STATE_LOADING = 0;
- /** 数据全部下载完成。Button应显示“安装”。 */
- public static final int STATE_COMPLETED = 1;
- /** 数据下载过程中被暂停。Button应显示“继续”。 */
- public static final int STATE_INTERRUPTED = 2;
- /** 下载安装包失败。Button应显示“失败”。 */
- public static final int STATE_DOWNLOAD_FAIL = 3;
- public String name;
- public long size;
- public long loadedSize;
- public String url;
- public int state;
- public boolean enable;
- }
lab.sodino.downloadbreak.ui.DontPressWithParentButton.java
- package lab.sodino.downloadbreak.ui;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.View;
- import android.widget.Button;
- /**
- * @author Sodino E-mail:sodinoopen@hotmail.com
- * @version Time:2011-6-5 下午08:37:27
- */
- public class DontPressWithParentButton extends Button {
- public DontPressWithParentButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public void setPressed(boolean pressed) {
- if (pressed && ((View) getParent()).isPressed()) {
- return;
- }
- super.setPressed(pressed);
- }
- }
lab.sodino.downloadbreak.util.NetworkTool.java
- package lab.sodino.downloadbreak.util;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.RandomAccessFile;
- import java.net.HttpURLConnection;
- import java.net.InetSocketAddress;
- import java.net.MalformedURLException;
- import java.net.URL;
- import lab.sodino.downloadbreak.ActDownload;
- import lab.sodino.downloadbreak.bean.BeanDownload;
- import android.content.Context;
- import android.net.ConnectivityManager;
- import android.net.NetworkInfo;
- import android.os.Handler;
- /**
- * 管理联网操作,包括管理url参数、下载APK包、获取任务字符串。<br/>
- *
- * @author Sodino E-mail:sodinoopen@hotmail.com
- * @version Time:2011-4-6 下午03:42:50
- */
- public class NetworkTool {
- /**
- * 开启一个HTTP链接。
- */
- public static HttpURLConnection openUrl(Context context, String urlStr) {
- LogOut.out("Network", "urlStr[" + urlStr + "]");
- URL urlURL = null;
- HttpURLConnection httpConn = null;
- try {
- urlURL = new URL(urlStr);
- // 需要android.permission.ACCESS_NETWORK_STATE
- // 在没有网络的情况下,返回值为null。
- NetworkInfo networkInfo = ((ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
- // 如果是使用的运营商网络
- if (networkInfo != null) {
- if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
- // 获取默认代理主机ip
- String host = android.net.Proxy.getDefaultHost();
- // 获取端口
- int port = android.net.Proxy.getDefaultPort();
- if (host != null && port != -1) {
- // 封装代理連接主机IP与端口号。
- InetSocketAddress inetAddress = new InetSocketAddress(host, port);
- // 根据URL链接获取代理类型,本链接适用于TYPE.HTTP
- java.net.Proxy.Type proxyType = java.net.Proxy.Type.valueOf(urlURL
- .getProtocol().toUpperCase());
- java.net.Proxy javaProxy = new java.net.Proxy(proxyType, inetAddress);
- httpConn = (HttpURLConnection) urlURL.openConnection(javaProxy);
- } else {
- httpConn = (HttpURLConnection) urlURL.openConnection();
- }
- } else {
- httpConn = (HttpURLConnection) urlURL.openConnection();
- }
- httpConn.setDoInput(true);
- } else {
- // LogOut.out(this, "No Avaiable Network");
- }
- } catch (NullPointerException npe) {
- npe.printStackTrace();
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return httpConn;
- }
- /** 启动链接并将RespondCode值返回。 */
- public static int connect(HttpURLConnection httpConn) {
- int code = -1;
- if (httpConn != null) {
- try {
- httpConn.connect();
- code = httpConn.getResponseCode();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- LogOut.out("NetworkTool", "respond_code=" + code);
- return code;
- }
- /**
- * 将指定的HTTP链接内容存储到指定的的文件中。<br/>
- * 返回值仅当参考。<br/>
- *
- * @param httpConn
- * @param filePath
- * 指定存储的文件路径。
- */
- public static boolean download2File(HttpURLConnection httpConn, String filePath) {
- boolean result = true;
- File file = new File(filePath);
- FileOutputStream fos = null;
- byte[] data = new byte[1024];
- int readLength = -1;
- InputStream is = null;
- try {
- fos = new FileOutputStream(file);
- is = httpConn.getInputStream();
- while ((readLength = is.read(data)) != -1) {
- fos.write(data, 0, readLength);
- fos.flush();
- }
- fos.flush();
- } catch (IOException ie) {
- result = false;
- ie.printStackTrace();
- } finally {
- try {
- if (is != null) {
- is.close();
- }
- if (fos != null) {
- fos.close();
- }
- } catch (IOException ie) {
- ie.printStackTrace();
- }
- }
- return result;
- }
- /**
- * 将bean资源下载。<br/>
- * 支持断点续传。
- *
- */
- public static void download2File(Context context, BeanDownload bean, Handler handler) {
- String filePath = ActDownload.RES_LOAD_FOLDER + bean.name + ".tmp";
- HttpURLConnection httpConn = null;
- File file = new File(filePath);
- RandomAccessFile randomFile = null;
- FileOutputStream fos = null;
- int dataBlockLength = 2048;
- byte[] data = new byte[dataBlockLength];
- int readLength = -1;
- InputStream is = null;
- try {
- if (bean.size <= 0) {
- bean.loadedSize = 0;
- if (file.getParentFile().exists() == false) {
- file.getParentFile().mkdirs();
- }
- if (file.exists() == false) {
- file.createNewFile();
- }
- // 采用普通的下载方式
- fos = new FileOutputStream(file);
- httpConn = openUrl(context, bean.url);
- int respondCode = connect(httpConn);
- LogOut.out("NetworkTool", "respondCode=" + respondCode);
- if (respondCode == HttpURLConnection.HTTP_OK) {
- bean.size = httpConn.getContentLength();
- is = httpConn.getInputStream();
- while ((readLength = is.read(data)) != -1 && bean.enable) {
- fos.write(data, 0, readLength);
- bean.loadedSize += readLength;
- handler.sendEmptyMessage(ActDownload.REFRESH);
- }
- }
- } else {
- // 采用断点续传方式
- randomFile = new RandomAccessFile(file, "rw");
- randomFile.setLength(bean.size);
- httpConn = openUrl(context, bean.url);
- httpConn.setRequestProperty("Range", "bytes=" + bean.loadedSize + "-"
- + (bean.size - 1));
- int respondCode = connect(httpConn);
- if (respondCode == HttpURLConnection.HTTP_PARTIAL) {
- is = httpConn.getInputStream();
- while ((readLength = is.read(data)) != -1 && bean.enable) {
- randomFile.seek(bean.loadedSize);
- randomFile.write(data, 0, readLength);
- bean.loadedSize += readLength;
- handler.sendEmptyMessage(ActDownload.REFRESH);
- }
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (is != null) {
- is.close();
- }
- if (httpConn != null) {
- disconnect(httpConn);
- }
- if (fos != null) {
- fos.close();
- }
- if (randomFile != null) {
- randomFile.close();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- /** 读取HttpURLConnection的数据并关闭相关流。 */
- public static byte[] fetchData_doClose(HttpURLConnection httpConn) {
- byte[] data = null;
- ByteArrayOutputStream baos = null;
- InputStream is = null;
- int read = -1;
- try {
- baos = new ByteArrayOutputStream();
- is = httpConn.getInputStream();
- while ((read = is.read()) != -1) {
- baos.write(read);
- }
- data = baos.toByteArray();
- } catch (IOException ie) {
- ie.printStackTrace();
- } finally {
- try {
- if (is != null) {
- is.close();
- }
- if (baos != null) {
- baos.close();
- }
- if (httpConn != null) {
- httpConn.disconnect();
- }
- } catch (IOException ie) {
- ie.printStackTrace();
- }
- }
- return data;
- }
- public static void disconnect(HttpURLConnection httpConn) {
- if (httpConn != null) {
- httpConn.disconnect();
- }
- }
- }
/res/drawable/l_download.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dip"
- android:layout_marginBottom="0dip">
- <LinearLayout android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_weight="1"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="10dip">
- <LinearLayout android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dip"
- android:layout_marginBottom="10dip"
- android:orientation="horizontal">
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:id="@+id/txtName"
- android:singleLine="true"
- android:ellipsize="middle"
- android:textColor="#ffffffff"
- android:textSize="15sp"
- ></TextView>
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/txtProgress"
- android:textColor="#ffffffff"
- android:textSize="10sp"
- android:layout_marginRight="10dip"
- ></TextView>
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/txtSize"
- android:textColor="#ffffffff"
- android:textSize="10sp"
- ></TextView>
- </LinearLayout>
- <ProgressBar android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:id="@+id/progressBar"
- style="?android:attr/progressBarStyleHorizontal" mce_style="?android:attr/progressBarStyleHorizontal"
- ></ProgressBar>
- </LinearLayout>
- <lab.sodino.downloadbreak.ui.DontPressWithParentButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="60dip"
- android:minHeight="30dip"
- android:id="@+id/btnAction"
- android:textColor="#ff000000"
- android:textStyle="bold"
- android:layout_marginRight="5dip"
- android:layout_marginLeft="5dip"
- android:layout_marginTop="10dip"
- android:layout_marginBottom="10dip"
- android:focusable="false"
- android:focusableInTouchMode="false"
- ></lab.sodino.downloadbreak.ui.DontPressWithParentButton>
- </LinearLayout>
所要添加的权限:
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
本文内容归CSDN博客博主Sodino 所有
转载请注明出处: http://blog.csdn.net/sodino/archive/2011/06/09/6535278.aspx
附:HTTP1.1 Range与Content-Range范例说明
假设你要开发一个多线程下载工具,你会自然的想到把文件分割成多个部分,比如4个部分,然后创建4个线程,每个线程负责下载一个部分,如果文件大小为403个byte,那么你的分割方式可以为:0-99 (前100个字节),100-199(第二个100字节),200-299(第三个100字节),300-402(最后103个字节)。
分割完成,每个线程都明白自己的任务,比如线程3的任务是负责下载200-299这部分文件,现在的问题是:线程3发送一个什么样的请求报文,才能够保证只请求文件的200-299字节,而不会干扰其他线程的任务。这时,我们可以使用HTTP1.1的Range头。Range头域可以请求实体的一个或者多个子范围,Range的值为0表示第一个字节,也就是Range计算字节数是从0开始的:
表示头500个字节:Range: bytes=0-499
表示第二个500字节:Range: bytes=500-999
表示最后500个字节:Range: bytes=-500
表示500字节以后的范围:Range: bytes=500-
第一个和最后一个字节:Range: bytes=0-0,-1
同时指定几个范围:Range: bytes=500-600,601-999
所以,线程3发送的请求报文必须有这一行:
Range: bytes=200-299
服务器接收到线程3的请求报文,发现这是一个带有Range头的GET请求,如果一切正常,服务器的响应报文会有下面这行:
HTTP/1.1 206 OK
表示处理请求成功,响应报文还有这一行:
Content-Range: bytes 200-299/403
斜杠后面的403表示文件的大小,通常Content-Range的用法为:
. The first 500 bytes:
Content-Range: bytes 0-499/1234
. The second 500 bytes:
Content-Range: bytes 500-999/1234
. All except for the first 500 bytes:
Content-Range: bytes 500-1233/1234
. The last 500 bytes:
Content-Range: bytes 734-1233/1234
这篇关于Android网络资源下载时断点续传的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!