您好,登錄后才能下訂單哦!
這篇文章給大家介紹怎么在Android中利用AsyncTask實現一個多線程斷點續傳功能,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
一、實現原理
多線程下載首先要通過每個文件總的下載線程數(我這里設定5個)來確定每個線程所負責下載的起止位置。
long blockLength = mFileLength / DEFAULT_POOL_SIZE; for (int i = 0; i < DEFAULT_POOL_SIZE; i++) { long beginPosition = i * blockLength;//每條線程下載的開始位置 long endPosition = (i + 1) * blockLength;//每條線程下載的結束位置 if (i == (DEFAULT_POOL_SIZE - 1)) { endPosition = mFileLength;//如果整個文件的大小不為線程個數的整數倍,則最后一個線程的結束位置即為文件的總長度 } ...... }
這里需要注意的是,文件大小往往不是線程個數的整數倍,所以最后一個線程的結束位置需要設置為文件長度。
確定好每個線程的下載起止位置之后,需要設置http請求頭來下載文件的指定位置:
//設置下載的數據位置beginPosition字節到endPosition字節 Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition); request.addHeader(header_size);
以上是多線程下載的原理,但是還要實現斷點續傳需要在每次暫停之后記錄每個線程已下載的大小,下次繼續下載時從上次下載后的位置開始下載。一般項目中都會存數據庫中,我這里為了簡單起見直接存在了SharedPreferences中,已下載url和線程編號作為key值。
@Override protected void onPostExecute(Long aLong) { Log.i(TAG, "download success "); //下載完成移除記錄 mSharedPreferences.edit().remove(currentThreadIndex).commit(); } @Override protected void onCancelled() { Log.i(TAG, "download cancelled "); //記錄已下載大小current mSharedPreferences.edit().putLong(currentThreadIndex, current).commit(); }
下載的時候,首先獲取已下載位置,如果已經下載過,就從上次下載后的位置開始下載:
//獲取之前下載保存的信息,從之前結束的位置繼續下載 //這里加了判斷file.exists(),判斷是否被用戶刪除了,如果文件沒有下載完,但是已經被用戶刪除了,則重新下載 long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0); if(file.exists() && downedPosition != 0) { beginPosition = beginPosition + downedPosition; current = downedPosition; synchronized (mCurrentLength) { mCurrentLength += downedPosition; } }
二、完整代碼
package com.bbk.lling.multithreaddownload; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicHeader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private static final int DEFAULT_POOL_SIZE = 5; private static final int GET_LENGTH_SUCCESS = 1; //下載路徑 private String downloadPath = Environment.getExternalStorageDirectory() + File.separator + "download"; // private String mUrl = "http://ftp.neu.edu.cn/mirrors/eclipse/technology/epp/downloads/release/juno/SR2/eclipse-java-juno-SR2-linux-gtk-x86_64.tar.gz"; private String mUrl = "http://p.gdown.baidu.com/c4cb746699b92c9b6565cc65aa2e086552651f73c5d0e634a51f028e32af6abf3d68079eeb75401c76c9bb301e5fb71c144a704cb1a2f527a2e8ca3d6fe561dc5eaf6538e5b3ab0699308d13fe0b711a817c88b0f85a01a248df82824ace3cd7f2832c7c19173236"; private ProgressBar mProgressBar; private TextView mPercentTV; SharedPreferences mSharedPreferences = null; long mFileLength = 0; Long mCurrentLength = 0L; private InnerHandler mHandler = new InnerHandler(); //創建線程池 private Executor mExecutor = Executors.newCachedThreadPool(); private List<DownloadAsyncTask> mTaskList = new ArrayList<DownloadAsyncTask>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progressbar); mPercentTV = (TextView) findViewById(R.id.percent_tv); mSharedPreferences = getSharedPreferences("download", Context.MODE_PRIVATE); //開始下載 findViewById(R.id.begin).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { @Override public void run() { //創建存儲文件夾 File dir = new File(downloadPath); if (!dir.exists()) { dir.mkdir(); } //獲取文件大小 HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(mUrl); HttpResponse response = null; try { response = client.execute(request); mFileLength = response.getEntity().getContentLength(); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (request != null) { request.abort(); } } Message.obtain(mHandler, GET_LENGTH_SUCCESS).sendToTarget(); } }.start(); } }); //暫停下載 findViewById(R.id.end).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { for (DownloadAsyncTask task : mTaskList) { if (task != null && (task.getStatus() == AsyncTask.Status.RUNNING || !task.isCancelled())) { task.cancel(true); } } mTaskList.clear(); } }); } /** * 開始下載 * 根據待下載文件大小計算每個線程下載位置,并創建AsyncTask */ private void beginDownload() { mCurrentLength = 0L; mPercentTV.setVisibility(View.VISIBLE); mProgressBar.setProgress(0); long blockLength = mFileLength / DEFAULT_POOL_SIZE; for (int i = 0; i < DEFAULT_POOL_SIZE; i++) { long beginPosition = i * blockLength;//每條線程下載的開始位置 long endPosition = (i + 1) * blockLength;//每條線程下載的結束位置 if (i == (DEFAULT_POOL_SIZE - 1)) { endPosition = mFileLength;//如果整個文件的大小不為線程個數的整數倍,則最后一個線程的結束位置即為文件的總長度 } DownloadAsyncTask task = new DownloadAsyncTask(beginPosition, endPosition); mTaskList.add(task); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, String.valueOf(i)); } } /** * 更新進度條 */ synchronized public void updateProgress() { int percent = (int) Math.ceil((float)mCurrentLength / (float)mFileLength * 100); // Log.i(TAG, "downloading " + mCurrentLength + "," + mFileLength + "," + percent); if(percent > mProgressBar.getProgress()) { mProgressBar.setProgress(percent); mPercentTV.setText("下載進度:" + percent + "%"); if (mProgressBar.getProgress() == mProgressBar.getMax()) { Toast.makeText(MainActivity.this, "下載結束", Toast.LENGTH_SHORT).show(); } } } @Override protected void onDestroy() { for(DownloadAsyncTask task: mTaskList) { if(task != null && task.getStatus() == AsyncTask.Status.RUNNING) { task.cancel(true); } mTaskList.clear(); } super.onDestroy(); } /** * 下載的AsyncTask */ private class DownloadAsyncTask extends AsyncTask<String, Integer , Long> { private static final String TAG = "DownloadAsyncTask"; private long beginPosition = 0; private long endPosition = 0; private long current = 0; private String currentThreadIndex; public DownloadAsyncTask(long beginPosition, long endPosition) { this.beginPosition = beginPosition; this.endPosition = endPosition; } @Override protected Long doInBackground(String... params) { Log.i(TAG, "downloading"); String url = params[0]; currentThreadIndex = url + params[1]; if(url == null) { return null; } HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(url); HttpResponse response = null; InputStream is = null; RandomAccessFile fos = null; OutputStream output = null; try { //本地文件 File file = new File(downloadPath + File.separator + url.substring(url.lastIndexOf("/") + 1)); //獲取之前下載保存的信息,從之前結束的位置繼續下載 //這里加了判斷file.exists(),判斷是否被用戶刪除了,如果文件沒有下載完,但是已經被用戶刪除了,則重新下載 long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0); if(file.exists() && downedPosition != 0) { beginPosition = beginPosition + downedPosition; current = downedPosition; synchronized (mCurrentLength) { mCurrentLength += downedPosition; } } //設置下載的數據位置beginPosition字節到endPosition字節 Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition); request.addHeader(header_size); //執行請求獲取下載輸入流 response = client.execute(request); is = response.getEntity().getContent(); //創建文件輸出流 fos = new RandomAccessFile(file, "rw"); //從文件的size以后的位置開始寫入,其實也不用,直接往后寫就可以。有時候多線程下載需要用 fos.seek(beginPosition); byte buffer [] = new byte[1024]; int inputSize = -1; while((inputSize = is.read(buffer)) != -1) { fos.write(buffer, 0, inputSize); current += inputSize; synchronized (mCurrentLength) { mCurrentLength += inputSize; } this.publishProgress(); if (isCancelled()) { return null; } } } catch (MalformedURLException e) { Log.e(TAG, e.getMessage()); } catch (IOException e) { Log.e(TAG, e.getMessage()); } finally{ try{ /*if(is != null) { is.close(); }*/ if (request != null) { request.abort(); } if(output != null) { output.close(); } if(fos != null) { fos.close(); } } catch(Exception e) { e.printStackTrace(); } } return null; } @Override protected void onPreExecute() { Log.i(TAG, "download begin "); super.onPreExecute(); } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //更新界面進度條 updateProgress(); } @Override protected void onPostExecute(Long aLong) { Log.i(TAG, "download success "); //下載完成移除記錄 mSharedPreferences.edit().remove(currentThreadIndex).commit(); } @Override protected void onCancelled() { Log.i(TAG, "download cancelled "); //記錄已下載大小current mSharedPreferences.edit().putLong(currentThreadIndex, current).commit(); } @Override protected void onCancelled(Long aLong) { Log.i(TAG, "download cancelled(Long aLong)"); super.onCancelled(aLong); mSharedPreferences.edit().putLong(currentThreadIndex, current).commit(); } } private class InnerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case GET_LENGTH_SUCCESS : beginDownload(); break; } super.handleMessage(msg); } } }
關于怎么在Android中利用AsyncTask實現一個多線程斷點續傳功能就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。