Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線(xiàn)程斷點(diǎn)續(xù)傳下載
這篇博客是AsyncTask下載系列的最后一篇文章,前面寫(xiě)了關(guān)于斷點(diǎn)續(xù)傳的和多線(xiàn)程下載的博客,這篇是在前兩篇的基礎(chǔ)上面實(shí)現(xiàn)的,有興趣的可以去看下。
一、AsyncTask實(shí)現(xiàn)斷點(diǎn)續(xù)傳
二、AsyncTask實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)續(xù)傳
這里模擬應(yīng)用市場(chǎng)app下載實(shí)現(xiàn)了一個(gè)Demo,因?yàn)橹挥幸粋€(gè)界面,所以沒(méi)有將下載放到Service中,而是直接在Activity中創(chuàng)建。在正式的項(xiàng)目中,下載都是放到Service中,然后通過(guò)BroadCast通知界面更新進(jìn)度。
上代碼之前,先看下demo的運(yùn)行效果圖吧。

下面我們看代碼,這里每一個(gè)文件的下載定義一個(gè)Downloador來(lái)管理下載該文件的所有線(xiàn)程(暫停、下載等)。Downloador創(chuàng)建3個(gè)DownloadTask(這里定義每個(gè)文件分配3個(gè)線(xiàn)程下載)來(lái)下載該文件特定的起止位置。這里要通過(guò)文件的大小來(lái)計(jì)算每個(gè)線(xiàn)程所下載的起止位置,詳細(xì)可以參考《AsyncTask實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)續(xù)傳》。
1、Downloador類(lèi)
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: 任務(wù)下載器
* @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; //線(xiàn)程池大小為9
private static final int THREAD_NUM = 3; //每個(gè)文件3個(gè)線(xiàn)程下載
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; //待下載的應(yīng)用
private long downloadLength; //下載過(guò)程中記錄已下載大小
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();
}
/**
* 開(kāi)始下載
*/
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();
}
}
//計(jì)算出該文件已經(jīng)下載的總長(zhǎng)度
List<DownloadInfo> lists = DownloadInfoDAO.getInstance(context.getApplicationContext())
.getDownloadInfosByUrl(appContent.getUrl());
for (DownloadInfo info : lists) {
downloadLength += info.getDownloadLength();
}
//插入文件下載記錄到數(shù)據(jù)庫(kù)
DownloadFileDAO.getInstance(context.getApplicationContext()).insertDownloadFile(appContent);
Message.obtain(handler, GET_LENGTH_SUCCESS).sendToTarget();
}
}.start();
}
/**
* 開(kāi)始創(chuàng)建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;//每條線(xiàn)程下載的開(kāi)始位置
long endPosition = (i + 1) * blockLength;//每條線(xiàn)程下載的結(jié)束位置
if (i == (THREAD_NUM - 1)) {
endPosition = fileLength;//如果整個(gè)文件的大小不為線(xiàn)程個(gè)數(shù)的整數(shù)倍,則最后一個(gè)線(xiàn)程的結(jié)束位置即為文件的總長(zhǎng)度
}
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;
}
/**
* 添加已下載大小
* 多線(xiàn)程訪(fǎng)問(wèn)需加鎖
* @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); //上面計(jì)算有時(shí)候會(huì)有點(diǎn)誤差,算到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類(lèi)
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) {
//這里加判斷的作用是:如果還處于等待就暫停了,運(yùn)行到這里已經(jīng)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);
//從之前結(jié)束的位置繼續(xù)下載
//這里加了判斷file.exists(),判斷是否被用戶(hù)刪除了,如果文件沒(méi)有下載完,但是已經(jīng)被用戶(hù)刪除了,則重新下載
if(file.exists() && downloadInfo != null) {
if(downloadInfo.isDownloadSuccess() == 1) {
//下載完成直接結(jié)束
return null;
}
beginPosition = beginPosition + downloadInfo.getDownloadLength();
downloadLength = downloadInfo.getDownloadLength();
}
if(!file.exists()) {
//如果此task已經(jīng)下載完,但是文件被用戶(hù)刪除,則需要重新設(shè)置已下載長(zhǎng)度,重新下載
downloador.resetDownloadLength();
}
//設(shè)置下載的數(shù)據(jù)位置beginPosition字節(jié)到endPosition字節(jié)
Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition);
request.addHeader(header_size);
//執(zhí)行請(qǐng)求獲取下載輸入流
response = client.execute(request);
is = response.getEntity().getContent();
//創(chuàng)建文件輸出流
fos = new RandomAccessFile(file, "rw");
//從文件的size以后的位置開(kāi)始寫(xiě)入
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);
//如果暫停了,需要將下載信息存入數(shù)據(jù)庫(kù)
if (isCancelled()) {
if(downloadInfo == null) {
downloadInfo = new DownloadInfo();
}
downloadInfo.setUrl(url);
downloadInfo.setDownloadLength(downloadLength);
downloadInfo.setTaskId(taskId);
downloadInfo.setDownloadSuccess(0);
//保存下載信息到數(shù)據(jù)庫(kù)
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();
}
}
//執(zhí)行到這里,說(shuō)明該task已經(jīng)下載完了
if(downloadInfo == null) {
downloadInfo = new DownloadInfo();
}
downloadInfo.setUrl(url);
downloadInfo.setDownloadLength(downloadLength);
downloadInfo.setTaskId(taskId);
downloadInfo.setDownloadSuccess(1);
//保存下載信息到數(shù)據(jù)庫(kù)
downloadInfoDAO.insertDownloadInfo(downloadInfo);
return null;
}
}
Downloador和DownloadTask只這個(gè)例子的核心代碼,下面是關(guān)于數(shù)據(jù)庫(kù)的,因?yàn)橐獙?shí)現(xiàn)斷點(diǎn)續(xù)傳必須要在暫停的時(shí)候?qū)⒚總€(gè)線(xiàn)程下載的位置記錄下來(lái),方便下次繼續(xù)下載時(shí)讀取。這里有兩個(gè)表,一個(gè)是存放每個(gè)文件的下載狀態(tài)的,一個(gè)是存放每個(gè)文件對(duì)應(yīng)的每個(gè)線(xiàn)程的下載狀態(tài)的。
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: 數(shù)據(jù)庫(kù)幫助類(lèi)
* @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,文件下載狀態(tài)的數(shù)據(jù)庫(kù)操作類(lèi)
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: 每個(gè)文件下載狀態(tài)記錄的數(shù)據(jù)庫(kù)操作類(lèi)
* @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;
}
/**
* 獲取數(shù)據(jù)庫(kù)連接
* @return
*/
public SQLiteDatabase getConnection() {
SQLiteDatabase sqliteDatabase = null;
try {
sqliteDatabase= new DBHelper(context).getReadableDatabase();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return sqliteDatabase;
}
/**
* 插入數(shù)據(jù)
* @param appContent
*/
public void insertDownloadFile(AppContent appContent) {
if(appContent == null) {
return;
}
//如果本地已經(jīng)存在,直接修改
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();
}
}
}
/**
* 根據(jù)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;
}
/**
* 根據(jù)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,每個(gè)線(xiàn)程對(duì)應(yīng)下載狀態(tài)的數(shù)據(jù)庫(kù)操作類(lèi)
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: 每個(gè)單獨(dú)線(xiàn)程下載信息記錄的數(shù)據(jù)庫(kù)操作類(lèi)
* @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;
}
/**
* 獲取數(shù)據(jù)庫(kù)連接
* @return
*/
public SQLiteDatabase getConnection() {
SQLiteDatabase sqliteDatabase = null;
try {
sqliteDatabase= new DBHelper(context).getReadableDatabase();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return sqliteDatabase;
}
/**
* 插入數(shù)據(jù)
* @param downloadInfo
*/
public void insertDownloadInfo(DownloadInfo downloadInfo) {
if(downloadInfo == null) {
return;
}
//如果本地已經(jīng)存在,直接修改
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;
}
/**
* 根據(jù)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();
}
}
}
}
具體的界面和使用代碼我就不貼代碼了,代碼有點(diǎn)多。需要的可以下載Demo的源碼看看。
因?yàn)檫€沒(méi)有花太多時(shí)間去測(cè),里面難免會(huì)有些bug,如果大家發(fā)現(xiàn)什么問(wèn)題,歡迎留言探討,謝謝!
源碼下載:https://github.com/liuling07/MultiTaskAndThreadDownload
相關(guān)文章
Android自定義復(fù)合控件實(shí)現(xiàn)通用標(biāo)題欄
這篇文章主要為大家詳細(xì)介紹了Android自定義復(fù)合控件實(shí)現(xiàn)通用標(biāo)題欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
android實(shí)現(xiàn)菜單三級(jí)樹(shù)效果
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)菜單三級(jí)樹(shù)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
詳解Android中的NestedScrolling機(jī)制帶你玩轉(zhuǎn)嵌套滑動(dòng)
這篇文章主要給大家詳細(xì)解析了Android中的NestedScrolling機(jī)制,通過(guò)介紹該機(jī)制帶你玩轉(zhuǎn)Android中的嵌套滑動(dòng)效果,文中給出了詳細(xì)的示例代碼和介紹,需要的朋友們可以參考學(xué)習(xí),下面來(lái)一起看看吧。2017-05-05
詳解如何在Flutter中獲取設(shè)備標(biāo)識(shí)符
這篇文章主要為大家介紹了幾種通過(guò)Flutter讀取設(shè)備信息的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下2022-04-04
Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例
這篇文章主要介紹了Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Android Listview 滑動(dòng)過(guò)程中提示圖片重復(fù)錯(cuò)亂的原因及解決方法
android中l(wèi)istview是比較常見(jiàn)的組件,通過(guò)本文主要給大家分析Android中Listview滾動(dòng)過(guò)程造成的圖片顯示重復(fù)、錯(cuò)亂、閃爍的原因及解決方法,順便跟進(jìn)Listview的緩存機(jī)制,感興趣的朋友一起看下吧2016-08-08
Android開(kāi)發(fā)之ClipboardManager剪貼板功能示例
這篇文章主要介紹了Android開(kāi)發(fā)之ClipboardManager剪貼板功能,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android使用ClipboardManager實(shí)現(xiàn)剪貼板功能的相關(guān)操作技巧,需要的朋友可以參考下2017-03-03

