Android之——多线程断点续传下载示例
文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。主要思路:1.多线程下载:
首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1;
通过Http协议的Range字段实现下载文件的分段;
通过Java类RandomAccessFile可以实现文件的随机访问,利用seek方法定位的文件的指定位置;
由HttpUrlConnection获取流来进行流的读写,实现文件的存储;
在下载过程中利用Handler来向外传递下载的信息。
2.断点续传:
对于每一个线程利用一个DownloadInfo类来保存下载的信息,每次在下载过程中向数据库更新信息(我也有想过只在下载暂停时进行更新,但那样的话我们的进程被杀掉时信息就无法保存下来)。在进行下载之前去访问数据库是否有记录存在,如果没有执行第一次下载的初始化,如果存在记录但下载文件不存在时,删掉数据库中的记录之后进行第一次下载的初始化,如果有记录且文件存在,则从数据库中取出信息。
实现的效果如图,自己封装的类提供了开始,暂停,删除,以及重新下载的方法。还没来得及将工程穿上优快云,给大家一个百度云盘的下载地址:https://pan.baidu.com/s/1dD1Xo8T
package com.example.test;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* 利用数据库来记录下载信息
* @author acer
*/
public class DownLoadHelper extendsSQLiteOpenHelper{
privatestaticfinal String SQL_NAME ="download.db";
privatestaticfinal int DOWNLOAD_VERSION=1;
publicDownLoadHelper(Context context) {
super(context, SQL_NAME,null, DOWNLOAD_VERSION);
// TODO Auto-generated constructor stub
}
/**
* 在download.db数据库下创建一个download_info表存储下载信息
*/
@Override
publicvoidonCreate(SQLiteDatabase db) {
db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
+"start_pos integer, end_pos integer, compelete_size integer,url char)");
}
@Override
publicvoidonUpgrade(SQLiteDatabase db,intoldVersion,int newVersion) {
}
}
DownlaodSqlTool进行数据的插入更新删除等操作
package com.example.test;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
/**
*
* 数据库操作工具类
*/
public class DownlaodSqlTool {
privateDownLoadHelper dbHelper;
publicDownlaodSqlTool(Context context) {
dbHelper =newDownLoadHelper(context);
}
/**
* 创建下载的具体信息
*/
publicvoidinsertInfos(List<downloadinfo> infos) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
for(DownloadInfo info : infos) {
String sql ="insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
database.execSQL(sql, bindArgs);
}
}
/**
* 得到下载具体信息
*/
publicList<downloadinfo> getInfos(String urlstr) {
List<downloadinfo> list =newArrayList<downloadinfo>();
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql ="select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
Cursor cursor = database.rawQuery(sql,newString[] { urlstr });
while(cursor.moveToNext()) {
DownloadInfo info =newDownloadInfo(cursor.getInt(0),
cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
cursor.getString(4));
list.add(info);
}
returnlist;
}
/**
* 更新数据库中的下载信息
*/
publicvoidupdataInfos(intthreadId,int compeleteSize, String urlstr) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
String sql ="update download_info set compelete_size=? where thread_id=? and url=?";
Object[] bindArgs = { compeleteSize, threadId, urlstr };
database.execSQL(sql, bindArgs);
}
/**
* 关闭数据库
*/
publicvoidcloseDb() {
dbHelper.close();
}
/**
* 下载完成后删除数据库中的数据
*/
publicvoiddelete(String url) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
database.delete("download_info","url=?",new String[] { url });
}
}</downloadinfo></downloadinfo></downloadinfo></downloadinfo>
DownloadHttpTool进行网络下载的类
package com.example.test;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class DownloadHttpTool {
/**
*
* 利用Http协议进行多线程下载具体实践类
*/
privatestaticfinal String TAG = DownloadHttpTool.class.getSimpleName();
privateintthreadCount;//线程数量
privateString urlstr;//URL地址
privateContext mContext;
privateHandler mHandler;
privateList<downloadinfo> downloadInfos;//保存下载信息的类
privateString localPath;//目录
privateString fileName;//文件名
privateintfileSize;
privateDownlaodSqlTool sqlTool;//文件信息保存的数据库操作类
privateenumDownload_State {
Downloading, Pause, Ready;//利用枚举表示下载的三种状态
}
privateDownload_State state = Download_State.Ready;//当前下载状态
privateintglobalCompelete = 0;//所有线程下载的总数
publicDownloadHttpTool(intthreadCount, String urlString,
String localPath, String fileName, Context context, Handler handler) {
super();
this.threadCount = threadCount;
this.urlstr = urlString;
this.localPath = localPath;
this.mContext = context;
this.mHandler = handler;
this.fileName = fileName;
sqlTool =newDownlaodSqlTool(mContext);
}
//在开始下载之前需要调用ready方法进行配置
publicvoidready() {
Log.w(TAG,"ready");
globalCompelete =0;
downloadInfos = sqlTool.getInfos(urlstr);
if(downloadInfos.size() ==0) {
initFirst();
}else{
File file =newFile(localPath +"/"+ fileName);
if(!file.exists()) {
sqlTool.delete(urlstr);
initFirst();
}else{
fileSize = downloadInfos.get(downloadInfos.size() -1)
.getEndPos();
for(DownloadInfo info : downloadInfos) {
globalCompelete += info.getCompeleteSize();
}
Log.w(TAG,"globalCompelete:::"+ globalCompelete);
}
}
}
publicvoidstart() {
Log.w(TAG,"start");
if(downloadInfos !=null) {
if(state == Download_State.Downloading) {
return;
}
state = Download_State.Downloading;
for(DownloadInfo info : downloadInfos) {
Log.v(TAG,"startThread");
newDownloadThread(info.getThreadId(), info.getStartPos(),
info.getEndPos(), info.getCompeleteSize(),
info.getUrl()).start();
}
}
}
publicvoidpause() {
state = Download_State.Pause;
sqlTool.closeDb();
}
publicvoiddelete(){
compelete();
File file =newFile(localPath +"/"+ fileName);
file.delete();
}
publicvoidcompelete() {
sqlTool.delete(urlstr);
sqlTool.closeDb();
}
publicintgetFileSize() {
returnfileSize;
}
publicintgetCompeleteSize() {
returnglobalCompelete;
}
//第一次下载初始化
privatevoidinitFirst() {
Log.w(TAG,"initFirst");
try{
URL url =newURL(urlstr);
HttpURLConnection connection = (HttpURLConnection) url
.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
fileSize = connection.getContentLength();
Log.w(TAG,"fileSize::"+ fileSize);
File fileParent =newFile(localPath);
if(!fileParent.exists()) {
fileParent.mkdir();
}
File file =newFile(fileParent, fileName);
if(!file.exists()) {
file.createNewFile();
}
// 本地访问文件
RandomAccessFile accessFile =newRandomAccessFile(file,"rwd");
accessFile.setLength(fileSize);
accessFile.close();
connection.disconnect();
}catch(Exception e) {
e.printStackTrace();
}
intrange = fileSize / threadCount;
downloadInfos =newArrayList<downloadinfo>();
for(inti = 0; i < threadCount -1; i++) {
DownloadInfo info =newDownloadInfo(i, i * range, (i +1) * range
-1,0, urlstr);
downloadInfos.add(info);
}
DownloadInfo info =newDownloadInfo(threadCount -1, (threadCount -1)
* range, fileSize -1,0, urlstr);
downloadInfos.add(info);
sqlTool.insertInfos(downloadInfos);
}
//自定义下载线程
privateclassDownloadThread extendsThread {
privateintthreadId;
privateintstartPos;
privateintendPos;
privateintcompeleteSize;
privateString urlstr;
privateinttotalThreadSize;
publicDownloadThread(intthreadId,int startPos, int endPos,
intcompeleteSize, String urlstr) {
this.threadId = threadId;
this.startPos = startPos;
this.endPos = endPos;
totalThreadSize = endPos - startPos +1;
this.urlstr = urlstr;
this.compeleteSize = compeleteSize;
}
@Override
publicvoidrun() {
HttpURLConnection connection =null;
RandomAccessFile randomAccessFile =null;
InputStream is =null;
try{
randomAccessFile =newRandomAccessFile(localPath +"/"
+ fileName,"rwd");
randomAccessFile.seek(startPos + compeleteSize);
URL url =newURL(urlstr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range","bytes="
+ (startPos + compeleteSize) +"-"+ endPos);
is = connection.getInputStream();
byte[] buffer =newbyte[1024];
intlength = -1;
while((length = is.read(buffer)) != -1) {
randomAccessFile.write(buffer,0, length);
compeleteSize += length;
Message message = Message.obtain();
message.what = threadId;
message.obj = urlstr;
message.arg1 = length;
mHandler.sendMessage(message);
sqlTool.updataInfos(threadId, compeleteSize, urlstr);
Log.w(TAG,"Threadid::"+ threadId +" compelete::"
+ compeleteSize +" total::"+ totalThreadSize);
if(compeleteSize >= totalThreadSize) {
break;
}
if(state != Download_State.Downloading) {
break;
}
}
}catch(Exception e) {
e.printStackTrace();
}finally{
try{
if(is !=null) {
is.close();
}
randomAccessFile.close();
connection.disconnect();
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
}</downloadinfo></downloadinfo>
DownloadUtils提供下载向外接口方法类:
package com.example.test;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* 将下载方法封装在此类
* 提供下载,暂停,删除,以及重置的方法
*/
public class DownloadUtil {
privateDownloadHttpTool mDownloadHttpTool;
privateOnDownloadListener onDownloadListener;
privateintfileSize;
privateintdownloadedSize = 0;
@SuppressLint("HandlerLeak")
privateHandler mHandler =newHandler() {
@Override
publicvoidhandleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
intlength = msg.arg1;
synchronized(this) {//加锁保证已下载的正确性
downloadedSize += length;
}
if(onDownloadListener !=null) {
onDownloadListener.downloadProgress(downloadedSize);
}
if(downloadedSize >= fileSize) {
mDownloadHttpTool.compelete();
if(onDownloadListener !=null) {
onDownloadListener.downloadEnd();
}
}
}
};
publicDownloadUtil(intthreadCount, String filePath, String filename,
String urlString, Context context) {
mDownloadHttpTool =newDownloadHttpTool(threadCount, urlString,
filePath, filename, context, mHandler);
}
//下载之前首先异步线程调用ready方法获得文件大小信息,之后调用开始方法
publicvoidstart() {
newAsyncTask<void,void,=""void="">() {
@Override
protectedVoid doInBackground(Void... arg0) {
// TODO Auto-generated method stub
mDownloadHttpTool.ready();
returnnull;
}
@Override
protectedvoidonPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
fileSize = mDownloadHttpTool.getFileSize();
downloadedSize = mDownloadHttpTool.getCompeleteSize();
Log.w("Tag","downloadedSize::"+ downloadedSize);
if(onDownloadListener !=null) {
onDownloadListener.downloadStart(fileSize);
}
mDownloadHttpTool.start();
}
}.execute();
}
publicvoidpause() {
mDownloadHttpTool.pause();
}
publicvoiddelete(){
mDownloadHttpTool.delete();
}
publicvoidreset(){
mDownloadHttpTool.delete();
start();
}
publicvoidsetOnDownloadListener(OnDownloadListener onDownloadListener) {
this.onDownloadListener = onDownloadListener;
}
//下载回调接口
publicinterfaceOnDownloadListener {
publicvoiddownloadStart(intfileSize);
publicvoiddownloadProgress(intdownloadedSize);//记录当前所有线程下总和
publicvoiddownloadEnd();
}
}</void,>
在MainActivity
package com.example.test;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.test.DownloadUtil.OnDownloadListener;
public class MainActivity extendsFragmentActivity {
privatestaticfinal String TAG = MainActivity.class.getSimpleName();
privateProgressBar mProgressBar;
privateButton start;
privateButton pause;
privateButton delete;
privateButton reset;
privateTextView total;
privateintmax;
privateDownloadUtil mDownloadUtil;
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
start = (Button) findViewById(R.id.button_start);
pause = (Button) findViewById(R.id.button_pause);
delete = (Button) findViewById(R.id.button_delete);
reset = (Button) findViewById(R.id.button_reset);
total = (TextView) findViewById(R.id.textView_total);
String urlString ="https://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";
String localPath = Environment.getExternalStorageDirectory()
.getAbsolutePath() +"/local";
mDownloadUtil =newDownloadUtil(2, localPath,"abc.jpg", urlString,
this);
mDownloadUtil.setOnDownloadListener(newOnDownloadListener() {
@Override
publicvoiddownloadStart(intfileSize) {
// TODO Auto-generated method stub
Log.w(TAG,"fileSize::"+ fileSize);
max = fileSize;
mProgressBar.setMax(fileSize);
}
@Override
publicvoiddownloadProgress(intdownloadedSize) {
// TODO Auto-generated method stub
Log.w(TAG,"Compelete::"+ downloadedSize);
mProgressBar.setProgress(downloadedSize);
total.setText((int) downloadedSize *100/ max + "%");
}
@Override
publicvoiddownloadEnd() {
// TODO Auto-generated method stub
Log.w(TAG,"ENd");
}
});
start.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View arg0) {
// TODO Auto-generated method stub
mDownloadUtil.start();
}
});
pause.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View arg0) {
// TODO Auto-generated method stub
mDownloadUtil.pause();
}
});
delete.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View arg0) {
// TODO Auto-generated method stub
mDownloadUtil.delete();
}
});
reset.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View arg0) {
// TODO Auto-generated method stub
mDownloadUtil.reset();
}
});
}
}