Android編程開發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器實(shí)例
本文實(shí)例講述了Android編程開發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器。分享給大家供大家參考,具體如下:
使用多線程斷點(diǎn)續(xù)傳下載器在下載的時候多個線程并發(fā)可以占用服務(wù)器端更多資源,從而加快下載速度,在下載過程中記錄每個線程已拷貝數(shù)據(jù)的數(shù)量,如果下載中斷,比如無信號斷線、電量不足等情況下,這就需要使用到斷點(diǎn)續(xù)傳功能,下次啟動時從記錄位置繼續(xù)下載,可避免重復(fù)部分的下載。這里采用數(shù)據(jù)庫來記錄下載的進(jìn)度。
效果圖:
斷點(diǎn)續(xù)傳
1.斷點(diǎn)續(xù)傳需要在下載過程中記錄每條線程的下載進(jìn)度
2.每次下載開始之前先讀取數(shù)據(jù)庫,查詢是否有未完成的記錄,有就繼續(xù)下載,沒有則創(chuàng)建新記錄插入數(shù)據(jù)庫
3.在每次向文件中寫入數(shù)據(jù)之后,在數(shù)據(jù)庫中更新下載進(jìn)度
4.下載完成之后刪除數(shù)據(jù)庫中下載記錄
Handler傳輸數(shù)據(jù)
這個主要用來記錄百分比,每下載一部分?jǐn)?shù)據(jù)就通知主線程來記錄時間
1.主線程中創(chuàng)建的View只能在主線程中修改,其他線程只能通過和主線程通信,在主線程中改變View數(shù)據(jù)
2.我們使用Handler可以處理這種需求
主線程中創(chuàng)建Handler,重寫handleMessage()方法
新線程中使用Handler發(fā)送消息,主線程即可收到消息,并且執(zhí)行handleMessage()方法
動態(tài)生成新View
可實(shí)現(xiàn)多任務(wù)下載
1.創(chuàng)建XML文件,將要生成的View配置好
2.獲取系統(tǒng)服務(wù)LayoutInflater,用來生成新的View
3.使用inflate(int resource, ViewGroup root)方法生成新的View
4.調(diào)用當(dāng)前頁面中某個容器的addView,將新創(chuàng)建的View添加進(jìn)來
示例
進(jìn)度條樣式 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" > <!--進(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
<?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" > <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
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); //動態(tài)生成新View,獲取系統(tǒng)服務(wù)LayoutInflater,用來生成新的View inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); rootLinearLayout = (LinearLayout) findViewById(R.id.root); pathEditText = (EditText) findViewById(R.id.path); // 窗體創(chuàng)建之后, 查詢數(shù)據(jù)庫是否有未完成任務(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); } /** * 動態(tài)生成新View * 初始化表單數(shù)據(jù) * @param path */ private void createDownload(String path) { //獲取系統(tǒng)服務(wù)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(1); try { button.setOnClickListener(new MyListener(progressBar, textView, path)); //調(diào)用當(dāng)前頁面中某個容器的addView,將新創(chuàng)建的View添加進(jìn)來 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(), "下載過程中出現(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
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(); //設(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ù)的長度 Message msg = new Message(); msg.what = 0; msg.getData().putInt("fileLen", fileLen); handler.sendMessage(msg); //計(jì)算每個線程下載的字節(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 { // 如果沒有, 則創(chuàng)建一個新記錄存入 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ù)范圍, 會以服務(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); // 記錄每個線程已下載的數(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:
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:
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; } }
希望本文所述對大家Android程序設(shè)計(jì)有所幫助。
- android實(shí)現(xiàn)多線程下載文件(支持暫停、取消、斷點(diǎn)續(xù)傳)
- Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
- Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
- Android多線程斷點(diǎn)續(xù)傳下載功能實(shí)現(xiàn)代碼
- Android多線程斷點(diǎn)續(xù)傳下載示例詳解
- Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線程斷點(diǎn)續(xù)傳下載
- Android實(shí)現(xiàn)網(wǎng)絡(luò)多線程斷點(diǎn)續(xù)傳下載實(shí)例
- PC版與Android手機(jī)版帶斷點(diǎn)續(xù)傳的多線程下載
- Android 使用AsyncTask實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳
- android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能
相關(guān)文章
Android TextView實(shí)現(xiàn)帶鏈接文字事件監(jiān)聽的三種常用方式示例
這篇文章主要介紹了Android TextView實(shí)現(xiàn)帶鏈接文字事件監(jiān)聽的方法,結(jié)合實(shí)例形式分析了鏈接跳轉(zhuǎn)、setMovementMethod及布局屬性設(shè)置三種常用的實(shí)現(xiàn)方式,需要的朋友可以參考下2017-08-08Android創(chuàng)建服務(wù)之started service詳細(xì)介紹
這篇文章主要介紹了Android創(chuàng)建服務(wù)之started service,需要的朋友可以參考下2014-02-02用xutils3.0進(jìn)行下載項(xiàng)目更新
這篇文章主要介紹了用xutils3.0進(jìn)行下載項(xiàng)目更新的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-08-08Android NDK開發(fā)(C語言基本數(shù)據(jù)類型)
這篇文章主要介紹了Android NDK開發(fā)中,C語言基本數(shù)據(jù)類型,主要以C語言包含的數(shù)據(jù)類型及基本類型展開相關(guān)資料,需要的朋友可以參考一下2021-12-12