扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
怎么在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 Listtasks; 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 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 (); } 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{ 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 ListgetAll() { SQLiteDatabase database = getConnection(); List list = new ArrayList (); 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 ListgetDownloadInfosByUrl(String url) { if(TextUtils.isEmpty(url)) { return null; } SQLiteDatabase database = getConnection(); List list = new ArrayList (); 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实现一个多任务多线程断点续传下载功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流