首先介绍涉及的几个知识点:
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字节以后的范围
综合上面的知识就可实现多线程断点续传的下载。下面说一下实现的思路:
首先执行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 ,继续下载。