http://blog.csdn.net/furongkang/article/details/6838521
111
Android開發(fā)多線程斷點(diǎn)續(xù)傳下載器
分類: Android 2011-10-01 23:14 931人閱讀 評(píng)論(8) 收藏 舉報(bào)
使用多線程斷點(diǎn)續(xù)傳下載器在下載的時(shí)候多個(gè)線程并發(fā)可以占用服務(wù)器端更多資源,從而加快下載速度,在下載過(guò)程中記錄每個(gè)線程已拷貝數(shù)據(jù)的數(shù)量,如果下載中斷,比如無(wú)信號(hào)斷線、電量不足等情況下,這就需要使用到斷點(diǎn)續(xù)傳功能,下次啟動(dòng)時(shí)從記錄位置繼續(xù)下載,可避免重復(fù)部分的下載。這里采用數(shù)據(jù)庫(kù)來(lái)記錄下載的進(jìn)度。
效果圖
斷點(diǎn)續(xù)傳
1.斷點(diǎn)續(xù)傳需要在下載過(guò)程中記錄每條線程的下載進(jìn)度
2.每次下載開始之前先讀取數(shù)據(jù)庫(kù),查詢是否有未完成的記錄,有就繼續(xù)下載,沒(méi)有則創(chuàng)建新記錄插入數(shù)據(jù)庫(kù)
3.在每次向文件中寫入數(shù)據(jù)之后,在數(shù)據(jù)庫(kù)中更新下載進(jìn)度
4.下載完成之后刪除數(shù)據(jù)庫(kù)中下載記錄
Handler傳輸數(shù)據(jù)
這個(gè)主要用來(lái)記錄百分比,每下載一部分?jǐn)?shù)據(jù)就通知主線程來(lái)記錄時(shí)間
1.主線程中創(chuàng)建的View只能在主線程中修改,其他線程只能通過(guò)和主線程通信,在主線程中改變View數(shù)據(jù)
2.我們使用Handler可以處理這種需求
主線程中創(chuàng)建Handler,重寫handleMessage()方法
新線程中使用Handler發(fā)送消息,主線程即可收到消息,并且執(zhí)行handleMessage()方法
動(dòng)態(tài)生成新View
可實(shí)現(xiàn)多任務(wù)下載
1.創(chuàng)建XML文件,將要生成的View配置好
2.獲取系統(tǒng)服務(wù)LayoutInflater,用來(lái)生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
3.使用inflate(int resource, ViewGroup root)方法生成新的View
4.調(diào)用當(dāng)前頁(yè)面中某個(gè)容器的addView,將新創(chuàng)建的View添加進(jìn)來(lái)
示例
進(jìn)度條樣式 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"
>
<!--進(jìn)度條樣式默認(rèn)為圓形進(jìn)度條,水平進(jìn)度條需要配置style屬性,
?android:attr/progressBarStyleHorizontal -->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="20dp"
style="?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"
android:onClick="pause"
android:text="||"
/>
</LinearLayout>
頂部樣式 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"
android:id="@+id/root"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="請(qǐng)輸入下載路徑"
/>
<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
public class MainActivity extends Activity {
private LayoutInflater inflater;
private LinearLayout rootLinearLayout;
private EditText pathEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//動(dòng)態(tài)生成新View,獲取系統(tǒng)服務(wù)LayoutInflater,用來(lái)生成新的View
inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
rootLinearLayout = (LinearLayout) findViewById(R.id.root);
pathEditText = (EditText) findViewById(R.id.path);
// 窗體創(chuàng)建之后, 查詢數(shù)據(jù)庫(kù)是否有未完成任務(wù), 如果有, 創(chuàng)建進(jìn)度條等組件, 繼續(xù)下載
List<String> list = new InfoDao(this).queryUndone();
for (String path : list)
createDownload(path);
}
/**
* 下載按鈕
* @param view
*/
public void download(View view) {
String path = "http://192.168.1.199:8080/14_Web/" + pathEditText.getText().toString();
createDownload(path);
}
/**
* 動(dòng)態(tài)生成新View
* 初始化表單數(shù)據(jù)
* @param path
*/
private void createDownload(String path) {
//獲取系統(tǒng)服務(wù)LayoutInflater,用來(lái)生成新的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(1);
try {
button.setOnClickListener(new MyListener(progressBar, textView, path));
//調(diào)用當(dāng)前頁(yè)面中某個(gè)容器的addView,將新創(chuàng)建的View添加進(jìn)來(lái)
rootLinearLayout.addView(linearLayout);
} catch (Exception e) {
e.printStackTrace();
}
}
private final class MyListener implements OnClickListener {
private ProgressBar progressBar;
private TextView textView;
private int fileLen;
private Downloader downloader;
private String name;
/**
* 執(zhí)行下載
* @param progressBar //進(jìn)度條
* @param textView //百分比
* @param path //下載文件路徑
*/
public MyListener(ProgressBar progressBar, TextView textView, String path) {
this.progressBar = progressBar;
this.textView = textView;
name = path.substring(path.lastIndexOf("/") + 1);
downloader = new Downloader(getApplicationContext(), handler);
try {
downloader.download(path, 3);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "下載過(guò)程中出現(xiàn)異常", 0).show();
throw new RuntimeException(e);
}
}
//Handler傳輸數(shù)據(jù)
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
//獲取文件的大小
fileLen = msg.getData().getInt("fileLen");
//設(shè)置進(jìn)度條最大刻度:setMax()
progressBar.setMax(fileLen);
break;
case 1:
//獲取當(dāng)前下載的總量
int done = msg.getData().getInt("done");
//當(dāng)前進(jìn)度的百分比
textView.setText(name + "\t" + done * 100 / fileLen + "%");
//進(jìn)度條設(shè)置當(dāng)前進(jìn)度:setProgress()
progressBar.setProgress(done);
if (done == fileLen) {
Toast.makeText(getApplicationContext(), name + " 下載完成", 0).show();
//下載完成后退出進(jìn)度條
rootLinearLayout.removeView((View) progressBar.getParent().getParent());
}
break;
}
}
};
/**
* 暫停和繼續(xù)下載
*/
public void onClick(View v) {
Button pauseButton = (Button) v;
if ("||".equals(pauseButton.getText())) {
downloader.pause();
pauseButton.setText("▶");
} else {
downloader.resume();
pauseButton.setText("||");
}
}
}
}
Downloader.java
[java] view plaincopy
public class Downloader {
private int done;
private InfoDao dao;
private int fileLen;
private Handler handler;
private boolean isPause;
public Downloader(Context context, Handler handler) {
dao = new InfoDao(context);
this.handler = handler;
}
/**
* 多線程下載
* @param path 下載路徑
* @param thCount 需要開啟多少個(gè)線程
* @throws Exception
*/
public void download(String path, int thCount) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//設(shè)置超時(shí)時(shí)間
conn.setConnectTimeout(3000);
if (conn.getResponseCode() == 200) {
fileLen = conn.getContentLength();
String name = path.substring(path.lastIndexOf("/") + 1);
File file = new File(Environment.getExternalStorageDirectory(), name);
RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.setLength(fileLen);
raf.close();
//Handler發(fā)送消息,主線程接收消息,獲取數(shù)據(jù)的長(zhǎng)度
Message msg = new Message();
msg.what = 0;
msg.getData().putInt("fileLen", fileLen);
handler.sendMessage(msg);
//計(jì)算每個(gè)線程下載的字節(jié)數(shù)
int partLen = (fileLen + thCount - 1) / thCount;
for (int i = 0; i < thCount; i++)
new DownloadThread(url, file, partLen, i).start();
} else {
throw new IllegalArgumentException("404 path: " + path);
}
}
private final class DownloadThread extends Thread {
private URL url;
private File file;
private int partLen;
private int id;
public DownloadThread(URL url, File file, int partLen, int id) {
this.url = url;
this.file = file;
this.partLen = partLen;
this.id = id;
}
/**
* 寫入操作
*/
public void run() {
// 判斷上次是否有未完成任務(wù)
Info info = dao.query(url.toString(), id);
if (info != null) {
// 如果有, 讀取當(dāng)前線程已下載量
done += info.getDone();
} else {
// 如果沒(méi)有, 則創(chuàng)建一個(gè)新記錄存入
info = new Info(url.toString(), id, 0);
dao.insert(info);
}
int start = id * partLen + info.getDone(); // 開始位置 += 已下載量
int end = (id + 1) * partLen - 1;
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(3000);
//獲取指定位置的數(shù)據(jù),Range范圍如果超出服務(wù)器上數(shù)據(jù)范圍, 會(huì)以服務(wù)器數(shù)據(jù)末尾為準(zhǔn)
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.seek(start);
//開始讀寫數(shù)據(jù)
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024 * 10];
int len;
while ((len = in.read(buf)) != -1) {
if (isPause) {
//使用線程鎖鎖定該線程
synchronized (dao) {
try {
dao.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
raf.write(buf, 0, len);
done += len;
info.setDone(info.getDone() + len);
// 記錄每個(gè)線程已下載的數(shù)據(jù)量
dao.update(info);
//新線程中用Handler發(fā)送消息,主線程接收消息
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();
}
}
}
//暫停下載
public void pause() {
isPause = true;
}
//繼續(xù)下載
public void resume() {
isPause = false;
//恢復(fù)所有線程
synchronized (dao) {
dao.notifyAll();
}
}
}
Dao:
DBOpenHelper:
[java] view plaincopy
public class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper(Context context) {
super(context, "download.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
InfoDao:
[java] view plaincopy
public class InfoDao {
private DBOpenHelper helper;
public InfoDao(Context context) {
helper = new DBOpenHelper(context);
}
public void insert(Info info) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO info(path, thid, done) VALUES(?, ?, ?)", new Object[] { info.getPath(), info.getThid(), info.getDone() });
}
public void delete(String path, int thid) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("DELETE FROM info WHERE path=? AND thid=?", new Object[] { path, thid });
}
public void 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 = null;
if (c.moveToNext())
info = new Info(c.getString(0), c.getInt(1), c.getInt(2));
c.close();
return info;
}
public void deleteAll(String path, int len) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT SUM(done) FROM info WHERE path=?", new String[] { path });
if (c.moveToNext()) {
int result = c.getInt(0);
if (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);
List<String> pathList = new ArrayList<String>();
while (c.moveToNext())
pathList.add(c.getString(0));
c.close();
return pathList;
}
}