在上一節中,我們簡單介紹了如何建立多任務下載下傳,但那種還不能拿來實用,這一集我們重點通過代碼為大家展示如何建立多線程斷點續傳下載下傳,這在實際項目中很常用.
main.xml:
[html] view
plaincopy
<?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"
>
<edittext
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext"
android:text="http://gongxue.cn/yingyinkuaiche/uploadfiles_9323/201008/2010082909434077.mp3"
/>
<linearlayout
android:orientation="horizontal"
<button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/downbutton"
android:text="download"
/>
android:id="@+id/pausebutton"
android:enabled="false"
android:text="pause"
</linearlayout>
<progressbar
android:layout_height="18dp"
style="?android:attr/progressbarstylehorizontal"
android:id="@+id/progressbar"
<textview
android:layout_width="fill_parent"
android:id="@+id/textview"
android:gravity="center"
</linearlayout>
string.xml:
<resources>
<string name="hello">hello world, main!</string>
<string name="app_name">多線程斷點續傳下載下傳</string>
</resources>
androidmanifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sms.multithreaddownload"
android:versioncode="1"
android:versionname="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".main"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.main" />
<category android:name="android.intent.category.launcher" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner" />
</application>
<uses-sdk android:minsdkversion="8" />
<instrumentation
android:targetpackage="sms.multithreaddownload"
android:name="android.test.instrumentationtestrunner" />
<!-- 通路網絡的權限 -->
<uses-permission android:name="android.permission.internet"/>
<!-- sdcard寫資料的權限 -->
<uses-permission android:name="android.permission.write_external_storage"/>
</manifest>
activity程式:
[java] view
package sms.multithreaddownload;
import java.io.file;
import sms.multithreaddownload.bean.downloadlistener;
import sms.multithreaddownload.service.downloadservice;
import android.app.activity;
import android.os.bundle;
import android.os.environment;
import android.os.handler;
import android.os.message;
import android.view.view;
import android.view.view.onclicklistener;
import android.widget.button;
import android.widget.edittext;
import android.widget.progressbar;
import android.widget.textview;
import android.widget.toast;
public class main extends activity {
private edittext path;
private textview progress;
private progressbar progressbar;
private handler handler = new uihandler();
private downloadservice servcie;
private button downbutton;
private button pausebutton;
private final class uihandler extends handler {
@override
public void handlemessage(message msg) {
switch (msg.what) {
case 1:
int downloaded_size = msg.getdata().getint("size");
progressbar.setprogress(downloaded_size);
int result = (int) ((float) downloaded_size / progressbar.getmax() * 100);
progress.settext(result + "%");
if (progressbar.getmax() == progressbar.getprogress()) {
toast.maketext(getapplicationcontext(), "下載下傳完成", toast.length_long).show();
}
}
}
}
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);
path = (edittext) this.findviewbyid(r.id.edittext);
progress = (textview) this.findviewbyid(r.id.textview);
progressbar = (progressbar) this.findviewbyid(r.id.progressbar);
downbutton = (button) this.findviewbyid(r.id.downbutton);
pausebutton = (button) this.findviewbyid(r.id.pausebutton);
downbutton.setonclicklistener(new downloadbutton());
pausebutton.setonclicklistener(new pausebutton());
private final class downloadbutton implements view.onclicklistener {
public void onclick(view v) {
downloadtask task;
try {
task = new downloadtask(path.gettext().tostring());
servcie.ispause = false;
v.setenabled(false);
pausebutton.setenabled(true);
new thread(task).start();
} catch (exception e) {
e.printstacktrace();
public class pausebutton implements onclicklistener {
servcie.ispause = true;
v.setenabled(false);
downbutton.setenabled(true);
public void pause(view v) {
private final class downloadtask implements runnable {
public downloadtask(string target) throws exception {
if (environment.getexternalstoragestate().equals(environment.media_mounted)) {
file destination = environment.getexternalstoragedirectory();
servcie = new downloadservice(target, destination, 3, getapplicationcontext());
progressbar.setmax(servcie.filesize);
} else {
toast.maketext(getapplicationcontext(), "sd卡不存在或寫保護!", toast.length_long).show();
public void run() {
servcie.download(new downloadlistener() {
@override
public void ondownload(int downloaded_size) {
message message = new message();
message.what = 1;
message.getdata().putint("size", downloaded_size);
handler.sendmessage(message);
});
}
工具類:
package sms.multithreaddownload.bean;
import android.content.context;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqliteopenhelper;
public class dbhelper extends sqliteopenhelper {
public dbhelper(context context) {
super(context, "multidownload.db", null, 1);
public void oncreate(sqlitedatabase db) {
db.execsql("create table filedownloading(_id integer primary key autoincrement,downpath varchar(100),threadid integer,downlength integer)");
public void onupgrade(sqlitedatabase db, int oldversion, int newversion) {
// todo auto-generated method stub
public interface downloadlistener {
public void ondownload(int downloaded_size);
import java.io.inputstream;
import java.io.randomaccessfile;
import java.net.httpurlconnection;
import java.net.url;
import android.util.log;
public final class multithreaddownload implements runnable {
public int id;
private randomaccessfile savedfile;
private string path;
/* 目前已下載下傳量 */
public int currentdownloadsize = 0;
/* 下載下傳狀态 */
public boolean finished;
/* 用于監視下載下傳狀态 */
private final downloadservice downloadservice;
/* 線程下載下傳任務的起始點 */
public int start;
/* 線程下載下傳任務的結束點 */
private int end;
public multithreaddownload(int id, file savedfile, int block, string path, integer downlength, downloadservice downloadservice) throws exception {
this.id = id;
this.path = path;
if (downlength != null) this.currentdownloadsize = downlength;
this.savedfile = new randomaccessfile(savedfile, "rwd");
this.downloadservice = downloadservice;
start = id * block + currentdownloadsize;
end = (id + 1) * block;
public void run() {
try {
httpurlconnection conn = (httpurlconnection) new url(path).openconnection();
conn.setconnecttimeout(5000);
conn.setrequestmethod("get");
conn.setrequestproperty("range", "bytes=" + start + "-" + end); // 設定擷取資料的範圍
inputstream in = conn.getinputstream();
byte[] buffer = new byte[1024];
int len = 0;
savedfile.seek(start);
while (!downloadservice.ispause && (len = in.read(buffer)) != -1) {
savedfile.write(buffer, 0, len);
currentdownloadsize += len;
savedfile.close();
in.close();
conn.disconnect();
if (!downloadservice.ispause) log.i(downloadservice.tag, "thread " + (this.id + 1) + "finished");
finished = true;
} catch (exception e) {
e.printstacktrace();
throw new runtimeexception("file downloading error!");
service類:
package sms.multithreaddownload.service;
import java.util.hashmap;
import java.util.list;
import java.util.map;
import java.util.map.entry;
import java.util.uuid;
import java.util.concurrent.concurrenthashmap;
import java.util.regex.matcher;
import java.util.regex.pattern;
import sms.multithreaddownload.bean.dbhelper;
import sms.multithreaddownload.bean.multithreaddownload;
import android.database.cursor;
public class downloadservice {
public static final string tag = "tag";
/* 用于查詢資料庫 */
private dbhelper dbhelper;
/* 要下載下傳的檔案大小 */
public int filesize;
/* 每條線程需要下載下傳的資料量 */
private int block;
/* 儲存檔案地目錄 */
private file savedfile;
/* 下載下傳位址 */
/* 是否停止下載下傳 */
public boolean ispause;
/* 線程數 */
private multithreaddownload[] threads;
/* 各線程已經下載下傳的資料量 */
private map<integer, integer> downloadedlength = new concurrenthashmap<integer, integer>();
public downloadservice(string target, file destination, int thread_size, context context) throws exception {
dbhelper = new dbhelper(context);
this.threads = new multithreaddownload[thread_size];
this.path = target;
url url = new url(target);
httpurlconnection conn = (httpurlconnection) url.openconnection();
conn.setconnecttimeout(5000);
conn.setrequestmethod("get");
if (conn.getresponsecode() != 200) {
throw new runtimeexception("server no response!");
filesize = conn.getcontentlength();
if (filesize <= 0) {
throw new runtimeexception("file is incorrect!");
string filename = getfilename(conn);
if (!destination.exists()) destination.mkdirs();
// 建構一個同樣大小的檔案
this.savedfile = new file(destination, filename);
randomaccessfile doout = new randomaccessfile(savedfile, "rwd");
doout.setlength(filesize);
doout.close();
conn.disconnect();
// 計算每條線程需要下載下傳的資料長度
this.block = filesize % thread_size == 0 ? filesize / thread_size : filesize / thread_size + 1;
// 查詢已經下載下傳的記錄
downloadedlength = this.getdownloadedlength(path);
private map<integer, integer> getdownloadedlength(string path) {
sqlitedatabase db = dbhelper.getreadabledatabase();
string sql = "select threadid,downlength from filedownloading where downpath=?";
cursor cursor = db.rawquery(sql, new string[] { path });
map<integer, integer> data = new hashmap<integer, integer>();
while (cursor.movetonext()) {
data.put(cursor.getint(0), cursor.getint(1));
db.close();
return data;
private string getfilename(httpurlconnection conn) {
string filename = path.substring(path.lastindexof("/") + 1, path.length());
if (filename == null || "".equals(filename.trim())) {
string content_disposition = null;
for (entry<string, list<string>> entry : conn.getheaderfields().entryset()) {
if ("content-disposition".equalsignorecase(entry.getkey())) {
content_disposition = entry.getvalue().tostring();
}
matcher matcher = pattern.compile(".*filename=(.*)").matcher(content_disposition);
if (matcher.find()) filename = matcher.group(1);
filename = uuid.randomuuid().tostring() + ".tmp"; // 預設名
return filename;
public void download(downloadlistener listener) throws exception {
this.deletedownloading(); // 先删除上次的記錄,再重新添加
for (int i = 0; i < threads.length; i++) {
threads[i] = new multithreaddownload(i, savedfile, block, path, downloadedlength.get(i), this);
new thread(threads[i]).start();
this.savedownloading(threads);
while (!isfinish(threads)) {
thread.sleep(900);
if (listener != null) listener.ondownload(getdownloadedsize(threads));
this.updatedownloading(threads);
if (!this.ispause) this.deletedownloading();// 完成下載下傳之後删除本次下載下傳記錄
private void savedownloading(multithreaddownload[] threads) {
sqlitedatabase db = dbhelper.getwritabledatabase();
db.begintransaction();
for (multithreaddownload thread : threads) {
string sql = "insert into filedownloading(downpath,threadid,downlength) values(?,?,?)";
db.execsql(sql, new object[] { path, thread.id, 0 });
db.settransactionsuccessful();
} finally {
db.endtransaction();
db.close();
private void deletedownloading() {
string sql = "delete from filedownloading where downpath=?";
db.execsql(sql, new object[] { path });
private void updatedownloading(multithreaddownload[] threads) {
string sql = "update filedownloading set downlength=? where threadid=? and downpath=?";
db.execsql(sql, new string[] { thread.currentdownloadsize + "", thread.id + "", path });
private int getdownloadedsize(multithreaddownload[] threads) {
int sum = 0;
for (int len = threads.length, i = 0; i < len; i++) {
sum += threads[i].currentdownloadsize;
return sum;
private boolean isfinish(multithreaddownload[] threads) {
for (int len = threads.length, i = 0; i < len; i++) {
if (!threads[i].finished) {
return false;
return true;
return false;
運作效果:
源碼下載下傳位址