您好,登錄后才能下訂單哦!
怎么在Android中使用AsyncTask實現一個多任務多線程斷點續傳下載功能?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
1、Downloador類
package com.bbk.lling.multitaskdownload.downloador; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.bbk.lling.multitaskdownload.beans.AppContent; import com.bbk.lling.multitaskdownload.beans.DownloadInfo; import com.bbk.lling.multitaskdownload.db.DownloadFileDAO; import com.bbk.lling.multitaskdownload.db.DownloadInfoDAO; import com.bbk.lling.multitaskdownload.utils.DownloadUtils; 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * @Class: Downloador * @Description: 任務下載器 * @author: lling(www.cnblogs.com/liuling) * @Date: 2015/10/13 */ public class Downloador { public static final String TAG = "Downloador"; private static final int THREAD_POOL_SIZE = 9; //線程池大小為9 private static final int THREAD_NUM = 3; //每個文件3個線程下載 private static final int GET_LENGTH_SUCCESS = 1; public static final Executor THREAD_POOL_EXECUTOR = Executors.newFixedThreadPool(THREAD_POOL_SIZE); private List<DownloadTask> tasks; private InnerHandler handler = new InnerHandler(); private AppContent appContent; //待下載的應用 private long downloadLength; //下載過程中記錄已下載大小 private long fileLength; private Context context; private String downloadPath; public Downloador(Context context, AppContent appContent) { this.context = context; this.appContent = appContent; this.downloadPath = DownloadUtils.getDownloadPath(); } /** * 開始下載 */ public void download() { if(TextUtils.isEmpty(downloadPath)) { Toast.makeText(context, "未找到SD卡", Toast.LENGTH_SHORT).show(); return; } if(appContent == null) { throw new IllegalArgumentException("download content can not be null"); } new Thread() { @Override public void run() { //獲取文件大小 HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(appContent.getUrl()); HttpResponse response = null; try { response = client.execute(request); fileLength = response.getEntity().getContentLength(); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (request != null) { request.abort(); } } //計算出該文件已經下載的總長度 List<DownloadInfo> lists = DownloadInfoDAO.getInstance(context.getApplicationContext()) .getDownloadInfosByUrl(appContent.getUrl()); for (DownloadInfo info : lists) { downloadLength += info.getDownloadLength(); } //插入文件下載記錄到數據庫 DownloadFileDAO.getInstance(context.getApplicationContext()).insertDownloadFile(appContent); Message.obtain(handler, GET_LENGTH_SUCCESS).sendToTarget(); } }.start(); } /** * 開始創建AsyncTask下載 */ private void beginDownload() { Log.e(TAG, "beginDownload" + appContent.getUrl()); appContent.setStatus(AppContent.Status.WAITING); long blockLength = fileLength / THREAD_NUM; for (int i = 0; i < THREAD_NUM; i++) { long beginPosition = i * blockLength;//每條線程下載的開始位置 long endPosition = (i + 1) * blockLength;//每條線程下載的結束位置 if (i == (THREAD_NUM - 1)) { endPosition = fileLength;//如果整個文件的大小不為線程個數的整數倍,則最后一個線程的結束位置即為文件的總長度 } DownloadTask task = new DownloadTask(i, beginPosition, endPosition, this, context); task.executeOnExecutor(THREAD_POOL_EXECUTOR, appContent.getUrl()); if(tasks == null) { tasks = new ArrayList<DownloadTask>(); } tasks.add(task); } } /** * 暫停下載 */ public void pause() { for (DownloadTask task : tasks) { if (task != null && (task.getStatus() == AsyncTask.Status.RUNNING || !task.isCancelled())) { task.cancel(true); } } tasks.clear(); appContent.setStatus(AppContent.Status.PAUSED); DownloadFileDAO.getInstance(context.getApplicationContext()).updateDownloadFile(appContent); } /** * 將已下載大小歸零 */ protected synchronized void resetDownloadLength() { this.downloadLength = 0; } /** * 添加已下載大小 * 多線程訪問需加鎖 * @param size */ protected synchronized void updateDownloadLength(long size){ this.downloadLength += size; //通知更新界面 int percent = (int)((float)downloadLength * 100 / (float)fileLength); appContent.setDownloadPercent(percent); if(percent == 100 || downloadLength == fileLength) { appContent.setDownloadPercent(100); //上面計算有時候會有點誤差,算到percent=99 appContent.setStatus(AppContent.Status.FINISHED); DownloadFileDAO.getInstance(context.getApplicationContext()).updateDownloadFile(appContent); } Intent intent = new Intent(Constants.DOWNLOAD_MSG); if(appContent.getStatus() == AppContent.Status.WAITING) { appContent.setStatus(AppContent.Status.DOWNLOADING); } Bundle bundle = new Bundle(); bundle.putParcelable("appContent", appContent); intent.putExtras(bundle); context.sendBroadcast(intent); } protected String getDownloadPath() { return downloadPath; } private class InnerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case GET_LENGTH_SUCCESS : beginDownload(); break; } super.handleMessage(msg); } } }
2、DownloadTask類
package com.bbk.lling.multitaskdownload.downloador; import android.content.Context; import android.os.AsyncTask; import android.util.Log; import com.bbk.lling.multitaskdownload.beans.DownloadInfo; import com.bbk.lling.multitaskdownload.db.DownloadInfoDAO; 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; /** * @Class: DownloadTask * @Description: 文件下載AsyncTask * @author: lling(www.cnblogs.com/liuling) * @Date: 2015/10/13 */ public class DownloadTask extends AsyncTask<String, Integer , Long> { private static final String TAG = "DownloadTask"; private int taskId; private long beginPosition; private long endPosition; private long downloadLength; private String url; private Downloador downloador; private DownloadInfoDAO downloadInfoDAO; public DownloadTask(int taskId, long beginPosition, long endPosition, Downloador downloador, Context context) { this.taskId = taskId; this.beginPosition = beginPosition; this.endPosition = endPosition; this.downloador = downloador; downloadInfoDAO = DownloadInfoDAO.getInstance(context.getApplicationContext()); } @Override protected void onPreExecute() { Log.e(TAG, "onPreExecute"); } @Override protected void onPostExecute(Long aLong) { Log.e(TAG, url + "taskId:" + taskId + "executed"); // downloador.updateDownloadInfo(null); } @Override protected void onProgressUpdate(Integer... values) { //通知downloador增加已下載大小 // downloador.updateDownloadLength(values[0]); } @Override protected void onCancelled() { Log.e(TAG, "onCancelled"); // downloador.updateDownloadInfo(null); } @Override protected Long doInBackground(String... params) { //這里加判斷的作用是:如果還處于等待就暫停了,運行到這里已經cancel了,就直接退出 if(isCancelled()) { return null; } url = params[0]; if(url == null) { return null; } HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(url); HttpResponse response; InputStream is; RandomAccessFile fos = null; OutputStream output = null; DownloadInfo downloadInfo = null; try { //本地文件 File file = new File(downloador.getDownloadPath() + File.separator + url.substring(url.lastIndexOf("/") + 1)); //獲取之前下載保存的信息 downloadInfo = downloadInfoDAO.getDownloadInfoByTaskIdAndUrl(taskId, url); //從之前結束的位置繼續下載 //這里加了判斷file.exists(),判斷是否被用戶刪除了,如果文件沒有下載完,但是已經被用戶刪除了,則重新下載 if(file.exists() && downloadInfo != null) { if(downloadInfo.isDownloadSuccess() == 1) { //下載完成直接結束 return null; } beginPosition = beginPosition + downloadInfo.getDownloadLength(); downloadLength = downloadInfo.getDownloadLength(); } if(!file.exists()) { //如果此task已經下載完,但是文件被用戶刪除,則需要重新設置已下載長度,重新下載 downloador.resetDownloadLength(); } //設置下載的數據位置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); downloadLength += inputSize; downloador.updateDownloadLength(inputSize); //如果暫停了,需要將下載信息存入數據庫 if (isCancelled()) { if(downloadInfo == null) { downloadInfo = new DownloadInfo(); } downloadInfo.setUrl(url); downloadInfo.setDownloadLength(downloadLength); downloadInfo.setTaskId(taskId); downloadInfo.setDownloadSuccess(0); //保存下載信息到數據庫 downloadInfoDAO.insertDownloadInfo(downloadInfo); return null; } } } catch (MalformedURLException e) { Log.e(TAG, e.getMessage()); } catch (IOException e) { Log.e(TAG, e.getMessage()); } finally{ try{ if (request != null) { request.abort(); } if(output != null) { output.close(); } if(fos != null) { fos.close(); } } catch(Exception e) { e.printStackTrace(); } } //執行到這里,說明該task已經下載完了 if(downloadInfo == null) { downloadInfo = new DownloadInfo(); } downloadInfo.setUrl(url); downloadInfo.setDownloadLength(downloadLength); downloadInfo.setTaskId(taskId); downloadInfo.setDownloadSuccess(1); //保存下載信息到數據庫 downloadInfoDAO.insertDownloadInfo(downloadInfo); return null; } }
Downloador和DownloadTask只這個例子的核心代碼,下面是關于數據庫的,因為要實現斷點續傳必須要在暫停的時候將每個線程下載的位置記錄下來,方便下次繼續下載時讀取。這里有兩個表,一個是存放每個文件的下載狀態的,一個是存放每個文件對應的每個線程的下載狀態的。
3、DBHelper
package com.bbk.lling.multitaskdownload.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * @Class: DBHelper * @Description: 數據庫幫助類 * @author: lling(www.cnblogs.com/liuling) * @Date: 2015/10/14 */ public class DBHelper extends SQLiteOpenHelper { public DBHelper(Context context) { super(context, "download.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table download_info(_id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, " + "download_length INTEGER, url VARCHAR(255), is_success INTEGER)"); db.execSQL("create table download_file(_id INTEGER PRIMARY KEY AUTOINCREMENT, app_name VARCHAR(255), " + "url VARCHAR(255), download_percent INTEGER, status INTEGER)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
4、DownloadFileDAO,文件下載狀態的數據庫操作類
package com.bbk.lling.multitaskdownload.db; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import android.util.Log; import com.bbk.lling.multitaskdownload.beans.AppContent; import java.util.ArrayList; import java.util.List; /** * @Class: DownloadFileDAO * @Description: 每個文件下載狀態記錄的數據庫操作類 * @author: lling(www.cnblogs.com/liuling) * @Date: 2015/10/13 */ public class DownloadFileDAO { private static final String TAG = "DownloadFileDAO"; private static DownloadFileDAO dao=null; private Context context; private DownloadFileDAO(Context context) { this.context=context; } synchronized public static DownloadFileDAO getInstance(Context context){ if(dao==null){ dao=new DownloadFileDAO(context); } return dao; } /** * 獲取數據庫連接 * @return */ public SQLiteDatabase getConnection() { SQLiteDatabase sqliteDatabase = null; try { sqliteDatabase= new DBHelper(context).getReadableDatabase(); } catch (Exception e) { Log.e(TAG, e.getMessage()); } return sqliteDatabase; } /** * 插入數據 * @param appContent */ public void insertDownloadFile(AppContent appContent) { if(appContent == null) { return; } //如果本地已經存在,直接修改 if(getAppContentByUrl(appContent.getUrl()) != null) { updateDownloadFile(appContent); return; } SQLiteDatabase database = getConnection(); try { String sql = "insert into download_file(app_name, url, download_percent, status) values (?,?,?,?)"; Object[] bindArgs = { appContent.getName(), appContent.getUrl(), appContent.getDownloadPercent() , appContent.getStatus().getValue()}; database.execSQL(sql, bindArgs); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } } } /** * 根據url獲取下載文件信息 * @param url * @return */ public AppContent getAppContentByUrl(String url) { if(TextUtils.isEmpty(url)) { return null; } SQLiteDatabase database = getConnection(); AppContent appContent = null; Cursor cursor = null; try { String sql = "select * from download_file where url=?"; cursor = database.rawQuery(sql, new String[] { url }); if (cursor.moveToNext()) { appContent = new AppContent(cursor.getString(1), cursor.getString(2)); appContent.setDownloadPercent(cursor.getInt(3)); appContent.setStatus(AppContent.Status.getByValue(cursor.getInt(4))); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } if (null != cursor) { cursor.close(); } } return appContent; } /** * 更新下載信息 * @param appContent */ public void updateDownloadFile(AppContent appContent) { if(appContent == null) { return; } SQLiteDatabase database = getConnection(); try { Log.e(TAG, "update download_file,app name:" + appContent.getName() + ",url:" + appContent.getUrl() + ",percent" + appContent.getDownloadPercent() + ",status:" + appContent.getStatus().getValue()); String sql = "update download_file set app_name=?, url=?, download_percent=?, status=? where url=?"; Object[] bindArgs = {appContent.getName(), appContent.getUrl(), appContent.getDownloadPercent() , appContent.getStatus().getValue(), appContent.getUrl()}; database.execSQL(sql, bindArgs); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } } } /** * 獲取所有下載文件記錄 * @return */ public List<AppContent> getAll() { SQLiteDatabase database = getConnection(); List<AppContent> list = new ArrayList<AppContent>(); Cursor cursor = null; try { String sql = "select * from download_file"; cursor = database.rawQuery(sql, null); while (cursor.moveToNext()) { AppContent appContent = new AppContent(cursor.getString(1), cursor.getString(2)); appContent.setDownloadPercent(cursor.getInt(3)); appContent.setStatus(AppContent.Status.getByValue(cursor.getInt(4))); list.add(appContent); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } if (null != cursor) { cursor.close(); } } return list; } /** * 根據url刪除記錄 * @param url */ public void delByUrl(String url) { if(TextUtils.isEmpty(url)) { return; } SQLiteDatabase database = getConnection(); try { String sql = "delete from download_file where url=?"; Object[] bindArgs = { url }; database.execSQL(sql, bindArgs); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } } } }
5、DownloadInfoDAO,每個線程對應下載狀態的數據庫操作類
package com.bbk.lling.multitaskdownload.db; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import android.util.Log; import com.bbk.lling.multitaskdownload.beans.DownloadInfo; import java.util.ArrayList; import java.util.List; /** * @Class: DownloadInfoDAO * @Description: 每個單獨線程下載信息記錄的數據庫操作類 * @author: lling(www.cnblogs.com/liuling) * @Date: 2015/10/13 */ public class DownloadInfoDAO { private static final String TAG = "DownloadInfoDAO"; private static DownloadInfoDAO dao=null; private Context context; private DownloadInfoDAO(Context context) { this.context=context; } synchronized public static DownloadInfoDAO getInstance(Context context){ if(dao==null){ dao=new DownloadInfoDAO(context); } return dao; } /** * 獲取數據庫連接 * @return */ public SQLiteDatabase getConnection() { SQLiteDatabase sqliteDatabase = null; try { sqliteDatabase= new DBHelper(context).getReadableDatabase(); } catch (Exception e) { Log.e(TAG, e.getMessage()); } return sqliteDatabase; } /** * 插入數據 * @param downloadInfo */ public void insertDownloadInfo(DownloadInfo downloadInfo) { if(downloadInfo == null) { return; } //如果本地已經存在,直接修改 if(getDownloadInfoByTaskIdAndUrl(downloadInfo.getTaskId(), downloadInfo.getUrl()) != null) { updateDownloadInfo(downloadInfo); return; } SQLiteDatabase database = getConnection(); try { String sql = "insert into download_info(task_id, download_length, url, is_success) values (?,?,?,?)"; Object[] bindArgs = { downloadInfo.getTaskId(), downloadInfo.getDownloadLength(), downloadInfo.getUrl(), downloadInfo.isDownloadSuccess()}; database.execSQL(sql, bindArgs); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } } } public List<DownloadInfo> getDownloadInfosByUrl(String url) { if(TextUtils.isEmpty(url)) { return null; } SQLiteDatabase database = getConnection(); List<DownloadInfo> list = new ArrayList<DownloadInfo>(); Cursor cursor = null; try { String sql = "select * from download_info where url=?"; cursor = database.rawQuery(sql, new String[] { url }); while (cursor.moveToNext()) { DownloadInfo info = new DownloadInfo(); info.setTaskId(cursor.getInt(1)); info.setDownloadLength(cursor.getLong(2)); info.setDownloadSuccess(cursor.getInt(4)); info.setUrl(cursor.getString(3)); list.add(info); } } catch (Exception e) { e.printStackTrace(); } finally { if (null != database) { database.close(); } if (null != cursor) { cursor.close(); } } return list; } /** * 根據taskid和url獲取下載信息 * @param taskId * @param url * @return */ public DownloadInfo getDownloadInfoByTaskIdAndUrl(int taskId, String url) { if(TextUtils.isEmpty(url)) { return null; } SQLiteDatabase database = getConnection(); DownloadInfo info = null; Cursor cursor = null; try { String sql = "select * from download_info where url=? and task_id=?"; cursor = database.rawQuery(sql, new String[] { url, String.valueOf(taskId) }); if (cursor.moveToNext()) { info = new DownloadInfo(); info.setTaskId(cursor.getInt(1)); info.setDownloadLength(cursor.getLong(2)); info.setDownloadSuccess(cursor.getInt(4)); info.setUrl(cursor.getString(3)); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } if (null != cursor) { cursor.close(); } } return info; } /** * 更新下載信息 * @param downloadInfo */ public void updateDownloadInfo(DownloadInfo downloadInfo) { if(downloadInfo == null) { return; } SQLiteDatabase database = getConnection(); try { String sql = "update download_info set download_length=?, is_success=? where task_id=? and url=?"; Object[] bindArgs = { downloadInfo.getDownloadLength(), downloadInfo.isDownloadSuccess(), downloadInfo.getTaskId(), downloadInfo.getUrl() }; database.execSQL(sql, bindArgs); } catch (Exception e) { Log.e(TAG, e.getMessage()); } finally { if (null != database) { database.close(); } } } }
看完上述內容,你們掌握怎么在Android中使用AsyncTask實現一個多任務多線程斷點續傳下載功能的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。