这篇博客是AsyncTask下载系列的最后一篇文章,前面写了关于断点续传的和多线程下载的博客,这篇是在前两篇的基础上面实现的,有兴趣的可以去看下。

这里模拟应用市场app下载实现了一个Demo,因为只有一个界面,所以没有将下载放到Service中,而是直接在Activity中创建。在正式的项目中,下载都是放到Service中,然后通过BroadCast通知界面更新进度。

上代码之前,先看下demo的运行效果图吧。

下面我们看代码,这里每一个文件的下载定义一个Downloador来管理下载该文件的所有线程(暂停、下载等)。Downloador创建3个DownloadTask(这里定义每个文件分配3个线程下载)来下载该文件特定的起止位置。这里要通过文件的大小来计算每个线程所下载的起止位置,详细可以参考《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 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 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 List getAll() {

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 List getDownloadInfosByUrl(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();

}

}

}

}

具体的界面和使用代码我就不贴代码了,代码有点多。需要的可以下载Demo的源码看看。

因为还没有花太多时间去测,里面难免会有些bug,如果大家发现什么问题,欢迎留言探讨,谢谢!

Android按下多任务java_Android 使用AsyncTask实现多任务多线程断点续传下载相关推荐

  1. OkHttp实现多线程断点续传下载,单例模式下多任务下载管理器,一起抛掉sp,sqlite的辅助吧

    丨版权说明 :<OkHttp实现多线程断点续传下载,单例模式下多任务下载管理器,一起抛掉sp,sqlite的辅助吧>于当前CSDN博客和乘月网属同一原创,转载请说明出处,谢谢. 最近项目需 ...

  2. android 多线程断点续传下载

    今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  3. android 多文件多线程断点续传下载

    今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  4. android 多线程断点续传下载 三

    android 多线程断点续传下载 三 转载于:https://www.cnblogs.com/zhujiabin/p/5660093.html

  5. android多线程下载原理,安卓多线程断点续传下载功能(靠谱第三方组件,原理demo)...

    一,原生的DownloadManager 从Android 2.3(API level 9)开始,Android以Service的方式提供了全局的DownloadManager来系统级地优化处理长时间 ...

  6. Android进阶:多线程断点续传下载

    今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的. 什么是多线程下载? 多线程下载其实就是迅雷, ...

  7. Android多线程断点续传下载原理及实现,移动开发工程师简历

    RandomAccessFile 文件写入 下面再讲讲文件写入问题,由于我们是多线程下载,因此文件并不是每次都是从前往后一个个字节写入的,随时可能在文件的任何一个地方写入数据.因此我们需要能够在文件的 ...

  8. android多线程断点续传下载

    csdn从老编辑器更换成新的编辑器,居然是重新发表一篇博客,好吧 http://blog.csdn.net/self_study/article/details/50505865

  9. Android之断点续传下载

    文件下载断点续传的意思是在文件下载中途可以多次暂停下载,然后又可以从刚才暂停的位置继续读取数据完成下载任务,并且最终下载下来的文件是与服务器上的资源是完整一致的,不会受损.由此可以确定,实现此功能需要 ...

  10. Android第5天,httpclient,多线程断点续传,进度条,xUtils

    httpclient 使用原始的httpclient public class MainActivity extends Activity {Handler handler = new Handler ...

最新文章

  1. Java中比较两个Double类型数据的大小
  2. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...
  3. Python编程基础:第二十四节 作用域Scope
  4. python关键字与标识符
  5. 「SVN」Linux下svn使用命令
  6. 为什么不早一点告诉我?——情场篇
  7. 微软:PowerShell 命令行工具存在 RCE 漏洞,请尽快修复
  8. redis的安装和命令的使用(史上最全命令集合)
  9. Java网络编程之客户端中的Socket
  10. 使用ipop共享串口提高工作效率
  11. 良心安利Unity3D U3D游戏源码素材网站
  12. MUI框架-01-介绍-准备-创建项目
  13. 改变世界的15个网站
  14. Apache Pegasus 首次 Meetup 圆满落幕
  15. 企业微信SCRM软件打造私域营销闭环?以保险行业为例
  16. x265-1.8版本-common/lowres.cpp注释
  17. 版主上路之 Application Server
  18. spring赌上未来的一击:WebFlux性能实测
  19. 浪潮信息AIStation联合智源研究院 帮助用户灵敏获取本地AI算力
  20. windows php gd 支持,windows服务器开启php的gd库方法

热门文章

  1. 解决chrome崩溃的方法
  2. Springboot集成通用Mapper与Pagehelper,实现mybatis+Druid的多数据源配置
  3. 使用Powershell 的获取别的机器WMI类失败解决方法!
  4. Missing iOS Distribution signing identity for …
  5. 6. JavaScript String 对象
  6. faster rcnn论文_论文导读-从Faster-RCNN/Mask RCNN/Cascade-RCNN到HTC
  7. Project Aposs
  8. vue项目打包,生成dist文件夹,如何修改文件夹的名字
  9. centos7下发邮件给自己的QQ邮箱
  10. GDI绘制矩形缺少右边和底部边界线问题