Android開發多線程斷點續傳下載器

來源:互聯網
上載者:User

使用多線程斷點續傳下載器在下載的時候多個線程並發可以佔用伺服器端更多資源,從而加快下載速度,在下載過程中記錄每個線程已拷貝資料的數量,如果下載中斷,比如無訊號斷線、電量不足等情況下,這就需要使用到斷點續傳功能,下次啟動時從屬記錄位置繼續下載,可避免重複部分的下載。這裡採用資料庫來記錄下載的進度。

       

 

斷點續傳

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

<?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"    >    <!--進度條樣式預設為圓形進度條,水平進度條需要配置style屬性,    ?android:attr/progressBarStyleHorizontal --><ProgressBarandroid:layout_width="fill_parent" android:layout_height="20dp"style="?android:attr/progressBarStyleHorizontal"/><TextViewandroid:layout_width="wrap_content" android:layout_height="wrap_content"android:layout_gravity="center"android:text="0%"/></LinearLayout><Buttonandroid:layout_width="40dp"    android:layout_height="40dp"    android:onClick="pause"    android:text="||"/></LinearLayout>

頂部樣式 main.xml

<?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="請輸入下載路徑"    /><LinearLayout     android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:layout_marginBottom="30dp"    ><EditTextandroid:id="@+id/path"android:layout_width="fill_parent"     android:layout_height="wrap_content"     android:singleLine="true"    android:layout_weight="1"/><Buttonandroid:layout_width="wrap_content"     android:layout_height="wrap_content"     android:text="下載"    android:onClick="download"    /></LinearLayout></LinearLayout> 

MainActivity.java

public class MainActivity extends Activity {private LayoutInflater inflater;private LinearLayout rootLinearLayout;private EditText pathEditText;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);//動態產生新View,擷取系統服務LayoutInflater,用來產生新的Viewinflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);rootLinearLayout = (LinearLayout) findViewById(R.id.root);pathEditText = (EditText) findViewById(R.id.path);// 表單建立之後, 查詢資料庫是否有未完成任務, 如果有, 建立進度條等組件, 繼續下載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);}/** * 動態產生新View * 初始化表單資料 * @param path */private void createDownload(String path) {//擷取系統服務LayoutInflater,用來產生新的ViewLayoutInflater 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));//調用當前頁面中某個容器的addView,將新建立的View添加進來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;/** * 執行下載 * @param progressBar //進度條 * @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(), "下載過程中出現異常", 0).show();throw new RuntimeException(e);}}//Handler傳輸資料private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 0://擷取檔案的大小fileLen = msg.getData().getInt("fileLen");//設定進度條最大刻度:setMax()progressBar.setMax(fileLen);break;case 1://擷取當前下載的總量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).show();//下載完成後退出進度條rootLinearLayout.removeView((View) progressBar.getParent().getParent());}break;}}};/** * 暫停和繼續下載 */public void onClick(View v) {Button pauseButton = (Button) v;if ("||".equals(pauseButton.getText())) {downloader.pause();pauseButton.setText("");} else {downloader.resume();pauseButton.setText("||");}}}}

Downloader.java

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 需要開啟多少個線程     * @throws Exception     */public void download(String path, int thCount) throws Exception {URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();//設定逾時時間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發送訊息,主線程接收訊息,擷取資料的長度Message msg = new Message();msg.what = 0;msg.getData().putInt("fileLen", fileLen);handler.sendMessage(msg);            //計算每個線程下載的位元組數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() {// 判斷上次是否有未完成任務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 - 1;try {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 = 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);// 記錄每個線程已下載的資料量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();}}}//暫停下載public void pause() {isPause = true;}//繼續下載public void resume() {isPause = false;//恢複所有線程synchronized (dao) {dao.notifyAll();}}}

 

 

Dao:

 

DBOpenHelper:

public class DBOpenHelper extends SQLiteOpenHelper {public DBOpenHelper(Context context) {super(context, "download.db", null, 1);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}

InfoDao:

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;}}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.