Android开发多线程断点续传
使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。
断点续传
1.断点续传需要在下载过程中记录每条线程的下载进度
2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库
3.在每次向文件中写入数据之后,在数据库中更新下载进度
4.下载完成之后删除数据库中下载记录
Handler传输数据
这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间
1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据
2.我们使用Handler可以处理这种需求
主线程中创建Handler,重写handleMessage()方法
新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法
动态生成新View
可实现多任务下载
1.创建XML文件,将要生成的View配置好
2.获取系统服务LayoutInflater,用来生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
3.使用inflate(int resource, ViewGroup root)方法生成新的View
4.调用当前页面中某个容器的addView,将新创建的View添加进来
示例
进度条样式 download.xml
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?><LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"><LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"><!--?android:attr/progressBarStyleHorizontal --><ProgressBar
android:layout_width="fill_parent"
android:layout_height="20dp"="?android:attr/progressBarStyleHorizontal"/><TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="0%"/></LinearLayout><Button
android:layout_width="40dp"
android:layout_height="40dp"="pause"="||"/></LinearLayout>
顶部样式 main.xml
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"="@+id/root"><TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"="请输入下载路径"/><LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"><EditText
android:id="@+id/path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight="1"/><Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="download"/></LinearLayout></LinearLayout>
MainActivity.java
[java] view plaincopy
publicclass MainActivity extendsprivateprivate LinearLayout rootLinearLayout;
private EditText pathEditText;
@Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//动态生成新View,获取系统服务LayoutInflater,用来生成新的View
inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
rootLinearLayout = (LinearLayout) findViewById(R.id.root);
pathEditText = (EditText) findViewById(R.id.path);
// 窗体创建之后, 查询数据库是否有未完成任务, 如果有, 创建进度条等组件, 继续下载
List<String> list =new InfoDao(thisfor (String path : list)
createDownload(path);
}
/**@param*/publicvoid download(View view) {
String path ="http://192.168.1.199:8080/14_Web/"+ pathEditText.getText().toString();
createDownload(path);
}
/**
* 动态生成新View
* 初始化表单数据
* @param*/privatevoid createDownload(String path) {
//获取系统服务LayoutInflater,用来生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download, null);
LinearLayout childLinearLayout = (LinearLayout) linearLayout.getChildAt(0);
ProgressBar progressBar = (ProgressBar) childLinearLayout.getChildAt(0);
TextView textView = (TextView) childLinearLayout.getChildAt(1);
Button button = (Button) linearLayout.getChildAt(1try {
button.setOnClickListener(new MyListener(progressBar, textView, path));
//调用当前页面中某个容器的addView,将新创建的View添加进来
rootLinearLayout.addView(linearLayout);
} catch (Exception e) {
e.printStackTrace();
}
}
privatefinalclass MyListener implementsprivateprivateprivateintprivateprivate/**
* 执行下载
* @param@param@param*/public MyListener(ProgressBar progressBar, TextView textView, String path) {
this.progressBar =this.textView = textView;
/") +1);
downloader =new Downloader(getApplicationContext(), handler);
try {
downloader.download(path, 3catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "下载过程中出现异常", 0thrownew RuntimeException(e);
}
}
//Handler传输数据
private Handler handler =new Handler() {
@Override
publicvoid handleMessage(Message msg) {
switchcase0//获取文件的大小
fileLen = msg.getData().getInt("fileLen"//设置进度条最大刻度:setMax()
progressBar.setMax(fileLen);
breakcase1//获取当前下载的总量
int done = msg.getData().getInt("done"//当前进度的百分比
textView.setText(name +"\t"+ done *100/ fileLen +"%"//进度条设置当前进度:setProgress()
progressBar.setProgress(done);
if (done == fileLen) {
Toast.makeText(getApplicationContext(), name +" 下载完成", 0//下载完成后退出进度条
rootLinearLayout.removeView((View) progressBar.getParent().getParent());
}
break;
}
}
};
/***/publicvoid onClick(View v) {
Button pauseButton =if ("||".equals(pauseButton.getText())) {
downloader.pause();
pauseButton.setText("?"else {
downloader.resume();
pauseButton.setText("||");
}
}
}
}
Downloader.java
[java] view plaincopy
publicclassprivateintprivateprivateintprivateprivatebooleanpublic Downloader(Context context, Handler handler) {
dao =newthis.handler =/**@param@param@throws*/publicvoid download(String path, int thCount) throws Exception {
URL url =new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout(3000if (conn.getResponseCode() ==200) {
fileLen = conn.getContentLength();
String /") +1);
File file =new File(Environment.getExternalStorageDirectory(), name);
RandomAccessFile raf =new RandomAccessFile(file, "rws");
raf.setLength(fileLen);
raf.close();
//Handler发送消息,主线程接收消息,获取数据的长度
Message msg =new Message();
msg.what =0;
msg.getData().putInt("fileLen", fileLen);
handler.sendMessage(msg);
//计算每个线程下载的字节数
int partLen = (fileLen + thCount -1) /for (int i =0; i < thCount; i++new DownloadThread(url, file, partLen, i).start();
} elsethrownew IllegalArgumentException("404 path: "+ path);
}
}
privatefinalclass DownloadThread extendsprivateprivateprivateintprivateintpublic DownloadThread(URL url, File file, int partLen, intthis.url =this.file =this.partLen =this.id =/***/publicvoid// 判断上次是否有未完成任务
Info info = dao.query(url.toString(), id);
if (info !=null// 如果有, 读取当前线程已下载量
done += info.getDone();
} else// 如果没有, 则创建一个新记录存入
info =new Info(url.toString(), id, 0);
dao.insert(info);
}
int start = id * partLen + info.getDone(); // 开始位置 += 已下载量
int end = (id +1) * partLen -1try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(3000//获取指定位置的数据,Range范围如果超出服务器上数据范围, 会以服务器数据末尾为准
conn.setRequestProperty("Range", "bytes="+ start +"-"+ end);
RandomAccessFile raf =new RandomAccessFile(file, "rws");
raf.seek(start);
//开始读写数据
InputStream in =byte[] buf =newbyte[1024*10intwhile ((len = in.read(buf)) !=-1if//使用线程锁锁定该线程
synchronizedtry {
dao.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
raf.write(buf, 0, len);
done += len;
info.setDone(info.getDone() +// 记录每个线程已下载的数据量
dao.update(info);
//新线程中用Handler发送消息,主线程接收消息
Message msg =new Message();
msg.what =1;
msg.getData().putInt("done", done);
handler.sendMessage(msg);
}
in.close();
raf.close();
// 删除下载记录
dao.deleteAll(info.getPath(), fileLen);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//暂停下载
publicvoid pause() {
isPause =true//继续下载
publicvoid resume() {
isPause =false//恢复所有线程
synchronized (dao) {
dao.notifyAll();
}
}
}
Dao:
DBOpenHelper:
[java] view plaincopy
publicclass DBOpenHelper extendspublic DBOpenHelper(Context context) {
super(context, "download.db", null, 1);
}
@Override
publicvoid onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");
}
@Override
publicvoid onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
InfoDao:
[java] view plaincopy
publicclassprivatepublic InfoDao(Context context) {
helper =new DBOpenHelper(context);
}
publicvoid insert(Info info) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO info(path, thid, done) VALUES(?, ?, ?)", new Object[] { info.getPath(), info.getThid(), info.getDone() });
}
publicvoid delete(String path, int thid) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("DELETE FROM info WHERE path=? AND thid=?", new Object[] { path, thid });
}
publicvoid update(Info info) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("UPDATE info SET done=? WHERE path=? AND thid=?", new Object[] { info.getDone(), info.getPath(), info.getThid() });
}
public Info query(String path, int thid) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT path, thid, done FROM info WHERE path=? AND thid=?", new String[] { path, String.valueOf(thid) });
Info info =nullif (c.moveToNext())
info =new Info(c.getString(0), c.getInt(1), c.getInt(2));
c.close();
returnpublicvoid deleteAll(String path, int len) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT SUM(done) FROM info WHERE path=?", newifint result = c.getInt(0if (result == len)
db.execSQL("DELETE FROM info WHERE path=? ", new Object[] { path });
}
}
public List<String> queryUndone() {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", null<String> pathList =new ArrayList<String>while (c.moveToNext())
pathList.add(c.getString(0));
c.close();
return pathList;
}
}