Android多线程断点续传下载的实现

首先介绍涉及的几个知识点:
1.HttpURLConnection 或者 HttpClient
   连接网络的部分可以使用上诉两个接口的任意一个即可,关键是要通过他们得到一个输入流。

2.AsyncTask
   它是Android提供的,用以处理异步操作的线程类。它的优势在于,开销比标准的Java线程小,并且可以直接通过它更新UI。通过使用它,每一个下载任务就是一个单独的线程,并且不会阻塞到UI线程。
    更多介绍可参阅此篇博文: http://lichen.blog.51cto.com/blog/697816/486868

3.RandomAccessFile
   它的seek方法可以实现在文件的特定位置开始写入数据。

4.Http协议的知识
    在构造请求对象的时候通过设置RANGE属性,来表示响应的数据范围。(注:RANGE的计数是由0开始的)
    比如:如果要下载的文件大小是 1000 byte,设置 RANGE: bytes=0-499 表示前500个 byte
                                                                         设置 RANGE: bytes =500-999 表示后500个比byte
                                                                         设置 RANGE: bytes = 500-  表示500字节以后的范围
     更多介绍可参阅此篇博文: http://www.blogjava.net/anchor110/articles/332189.html

综合上面的知识就可实现多线程断点续传的下载。下面说一下实现的思路:

   首先执行AsyncTask的excute方法,传入网络资源的链接,这时会先执行onPreExecute 方法,然后再开始执行doInBackground。
   在doInBackground方法中,通过网络接口,找到要下载的文件从而获得一个输入流,然后设置文件的路径,创建一个RandomAccessFile对象,开始下载文件。 (它实际是在另一个线程中被执行)

   在下载过程中,使用publishProgress方法来传递进度,这些进度会被onProgressUpdate方法接收,进行UI的更新。同时在下载循环体中设置一个boolean 标识符来实现暂停的功能,也就是让当前线程不停的休眠。

  当下载完成后,返回一个定义好的标识量,表示下载完成了;如果出错了,则返回一个另一个标识量。一旦返回了值,则跳出了doInBackground,也就结束了下载的过程。 这些返回的标识量会被doonPostExecute方法接受,通过判断不同的标识量,进行不同的操作。

 如果在下载途中,用户取消了下载,则停止当前的AsyncTask,也就是调用它的cancel方法。那么在doInBackground方法的下载循环体中,就要再添加一个if域来判断当前的AsyncTask是否被取消了,如果取消了则记录下当前的下载进度(这里为了简便直接用ShraedPreference来存储),并且返回标识量跳出doInBackground方法结束这个线程。

当用户再次点击下载的时候,就要获得下载的进度,也就是已经下载了多少byte,将它传递给AsyncTask的curSize成员变量,通过判断是否大于0,如果它大于0则表示这是一个开启过的任务,并且已经下载了curSize的数据,现在要在这个数据点后再继续下载。那么就要为这个Http请求对象设置RANGE属性(这里需要把CurSize-1)。同时要注意的是,当获得响应的时候,文件的长度=已经下载的长度+待下载的长度。 

下面的是实现的代码:


ublic class Main extends Activity implements OnClickListener {

        String url = "http://file16.top100.cn/201108292046/62BC4DB4591BDC3FC607364D644292D3/Special_337825/%E9%99%80%E9%A3%9B%E8%BC%AA.mp3";
String sdPath;
String fileName = "music12.mp3";

Button bt1;
Button bt2;
Button bt3;
Button bt4;

TextView info;
TextView progress;

ProgressBar progressBar;

DownloadTask downloadTask;

public static final String KEY_POSITION = "position";

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

sdPath = Environment.getExternalStorageDirectory().getPath();

bt1 = (Button) findViewById(R.id.bt_start);
bt1.setOnClickListener(this);

bt2 = (Button) findViewById(R.id.bt_pause_contintue);
bt2.setOnClickListener(this);

bt3 = (Button) findViewById(R.id.bt_cancel);
bt3.setOnClickListener(this);
bt4 = (Button) findViewById(R.id.bt4);
bt4.setOnClickListener(this);

info = (TextView) findViewById(R.id.info_textView);
progress = (TextView) findViewById(R.id.progress_textView);

progressBar = (ProgressBar) findViewById(R.id.progressBar);

downloadTask = new DownloadTask();
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_start:
downloadTask.execute(url);
break;

case R.id.bt_pause_contintue:
if (downloadTask.isPause()) {
downloadTask.setPause(false);
} else {
downloadTask.setPause(true);
}
break;

case R.id.bt_cancel:
downloadTask.cancel(true);
break;
case R.id.bt4:
downloadTask = new DownloadTask();
downloadTask.setCurSize(getInfo());
downloadTask.execute(url);
break;

default:
break;
}
}

public int getInfo() {
SharedPreferences sf = this.getPreferences(MODE_WORLD_WRITEABLE
| MODE_WORLD_READABLE);

return sf.getInt(KEY_POSITION, 0);
}

public void saveInfo(int position) {
SharedPreferences sf = this.getPreferences(MODE_WORLD_WRITEABLE
| MODE_WORLD_READABLE);

Editor editor = sf.edit();
editor.putInt(KEY_POSITION, position);
editor.commit();
}

class DownloadTask extends AsyncTask<String, Integer, Integer> {

public static final int RESULT_DOWNLOAD_OK = 1;
public static final int RESULT_DOWNLOAD_FAILED = -1;
public static final int RESULT_DOWNLOAD_CANCEL = 0;

private boolean isPause = false;
private static final int SLEEP_TIME = 500; //暂停是休眠的时间

private int curSize = 0; // 已经下载的长度
private int length = 0; // 总的长度
@Override
protected Integer doInBackground(String... params) { //这里是可以增长的参数

int result = RESULT_DOWNLOAD_FAILED;//默认结果标识量为下载失败

InputStream in = null;

HttpClient client = new DefaultHttpClient();

HttpGet request = null;

RandomAccessFile raf = null;

try {
request = new HttpGet(params[0]);
if(curSize>0)
{
request.setHeader("RANGE","bytes="+String.valueOf(curSize-1)+"-");
}
HttpResponse response = client.execute(request);
HttpEntity entity = response.getEntity();

length = (int) entity.getContentLength()+curSize; //获得下载文件的长度(如果是断点续传,则要加上已经下载的长度)

in = entity.getContent();

File file = new File(sdPath, fileName);
file.createNewFile();
raf = new RandomAccessFile(file, "rw");
raf.seek(curSize);//定位到数据写入点
byte[] buffer = new byte[128];//缓存数组
int ch = -1;

//下载循环体
while ((ch = in.read(buffer)) != -1) {
curSize += ch;
raf.write(buffer, 0, ch);

publishProgress((int) ((curSize / (float) length) * 100)); //发布进度

while (isPause) // 暂停
{
Thread.currentThread().sleep(SLEEP_TIME);
}

if (isCancelled()) // 取消
{
saveInfo(curSize);//保存下载进度
return RESULT_DOWNLOAD_CANCEL;

}
}

entity.consumeContent();//释放HttpEntity占用的内存资源
result = RESULT_DOWNLOAD_OK;
} catch (Exception e) {
e.printStackTrace();
} finally {//关闭流   
try {if (in != null) {in.close();}
if (raf != null) {raf.close();}
if (request != null) {request.abort();}
} catch (Exception e2) {
e2.printStackTrace();
}
}

return result;
}


//下载开始前
@Override
protected void onPreExecute() {
info.setText("正在下载```");
}

//跟新进度
@Override
protected void onProgressUpdate(Integer... values) {//可增长的参数
progressBar.setProgress(values[0]);
progress.setText("已完成: " + values[0] + " %");

}

//下载完成后
@Override
protected void onPostExecute(Integer result) {
switch (result) {
case RESULT_DOWNLOAD_OK:
info.setText("下载成功");
break;

case RESULT_DOWNLOAD_FAILED:
info.setText("出错啦");
break;

case RESULT_CANCELED:
info.setText("下载被取消了");
break;

default:
break;
}
}

public boolean isPause() {
return isPause;
}

public void setPause(boolean isPause) {
this.isPause = isPause;
}
public void setCurSize(int curSize) {
this.curSize = curSize;
}
}
}

布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="下载状态信息"
    android:id="@+id/info_textView"
    />
    
    <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal">
    
    <Button android:id="@+id/bt_start"  android:text="下载" android:layout_width="wrap_content" android:layout_height="wrap_content"  />
    <Button android:id="@+id/bt_pause_contintue"  android:text="暂停" android:layout_width="wrap_content" android:layout_height="wrap_content"  />
    <Button android:id="@+id/bt_cancel"  android:text="取消" android:layout_width="wrap_content" android:layout_height="wrap_content"  />
    <Button android:id="@+id/bt4" android:text="继续下载" android:layout_width="wrap_content" android:layout_height="wrap_content"  />
    </LinearLayout>
    
    <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:id="@+id/progressBar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="100" android:progress="0" />
    <TextView android:id="@+id/progress_textView" android:text="下载进度:" android:layout_width="fill_parent" android:layout_height="wrap_content" />
</LinearLayout>

别忘了加权限:
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

这样就基本实现了可断点续传的下载。



扩展:如果要对同一个文件进行多个线程的下载,那么就要先把这个文件分成不同的分块,然后开启多个线程来下载,通过RANGE头为每个线程的规定下载的数据范围,并且每个文件块要使用单独的一个缓存文件来存储,当全部线程都完成下载后,再把它们都拼接起来。

当下载被停止后,则要记录下每个线程的进度,分块大小,文件大小,缓存文件的信息等(这里就必须要用数据库来完成)。

当再次开启下载任务的时候,则读取数据库中信息,开启每一条下载线程,并设置新的RANGE ,继续下载。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值