Android 多线程下载文件原理霸气解析介绍 (完结版)-----greendao数据库的存储,断点下载
这篇文章主要介绍一下,如果暂停了,或者网段了,然后有恢复了,接着之前的继续下载。
首先我们先添加数据库 greendao
步骤1:首先在项目中创建一个java的Modle。如图
步骤2:创建DbGenerate文件,
public class DbGenerate {
public static void main(String args[]) {
// 版本号 \ 包名
Schema schema = new Schema(1, "com.imooc.db");
Entity entity = schema.addEntity("DownloadEntity");
// 设置键名称
entity.addLongProperty("start_position");
entity.addLongProperty("end_position");
entity.addLongProperty("progress_position");
entity.addStringProperty("download_url");
entity.addIntProperty("thread_id");
// 设置主键
entity.addIdProperty().autoincrement();
try {
new DaoGenerator().generateAll(schema, "dbgenerate/src-gen");
} catch (Exception e) {
e.printStackTrace();
}
}
}
步骤3:然后创建 “src-gen”目录空文件夹
步骤4:重新编译项目,src-gen里面会出现4个文件,拷贝到项目中直接用就可以了。
步骤5:在使用的项目的gradle中添加,完成
compile ‘de.greenrobot:greendao-generator:2.1.0’
compile ‘de.greenrobot:greendao:2.1.0’
数据加进来完成之后,下面我们看在这个下载案例中如何使用。
**先创建DownloadHelper操作类 public class DownloadHelper {
private DaoMaster mMaster;
private DaoSession mSession;
private DownloadEntityDao mDao;
private static DownloadHelper sHelper = new DownloadHelper();
public static DownloadHelper getInstance() {
return sHelper;
}
private DownloadHelper() {
}
/**
* 初始化、创建数据库
* @param context
*/
public void init(Context context) {
// 数据库名
SQLiteDatabase db = new DaoMaster.DevOpenHelper(context, "download.db", null).getWritableDatabase();
mMaster = new DaoMaster(db);
mSession = mMaster.newSession();
mDao = mSession.getDownloadEntityDao();
}
/**
* 保存数据
* @param entity
*/
public void insert(DownloadEntity entity) {
mDao.insertOrReplace(entity);
}
/**
* 获取全部数据
* @param url
* @return
*/
public List<DownloadEntity> getAll(String url) {
return mDao.queryBuilder().where(DownloadEntityDao.Properties.Download_url.eq(url)).orderAsc(DownloadEntityDao.Properties.Thread_id).list();
}
}1、首先在DownloadManager中的downLoad下载方法中,首先读取一下数据库,有没有数据,如果有数据按之前的逻辑走,正常下载,如果没有,就查询数据库数据,接着下载。然后更新进度条*
/** * 判断每个线程下载多长的数据,并多线程下载 */
public void downLoad(final String url, final DownLoadCallBack callBack){
// 下载之前先查询一下数据库有没有之前下载的
mCache = DownloadHelper.getInstance().getAll(url);
if(mCache == null || mCache.size() == 0) {
// 如果之前没有下载,没有缓存正常走
HttpManager.getInstance().asyncRequest(url, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response == null && callBack != null) {
callBack.fail(HttpManager.NETWORK_ERROR_CODE, "网络出问题了");
return;
}
length = response.body().contentLength();
if(length == -1) {
// 获取不到文件的总长度
callBack.fail(HttpManager.CONTENT_LENGTH_ERROR_CODE, "contenLength -1");
return;
}
processDownload(url, length, callBack, mCache);
}
});
}else {
// 之前有下载逻辑处理
for(int i = 0; i < mCache.size(); i++) {
// 取出每个线程存储的下载长度,从新下载
DownloadEntity entity = mCache.get(i);
long startSize = entity.getStart_position() + entity.getProgress_position();
long endSize = entity.getEnd_position();
sThreadPool.execute(new DownloadRunnable(startSize, endSize, url, callBack, entity));
// 获取文件的总长度,进度下载用到
if(i == mCache.size() - 1) {
length = mCache.get(i).getEnd_position() + 1;
}
}
}
// TODO: 更新下载进度
sLocalProgressPool.execute(new Runnable() {
@Override
public void run() {
while (true){
try {
// 每隔500更新一次
Thread.sleep(500);
// 取到下载的文件
File file = FlieStorageManager.getInstance().getFileByName(url);
long fileSize = file.length();
// 计算下载的百分比
int progress = (int) (fileSize * 100 / length);
callBack.progress(progress);
// 下载完退出线程、循环
if(progress >= 100) {
callBack.progress(progress);
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
2、然后看一下我们下载保存操作:
/** * 下载 * @param length 下载文件的长度 保存数据 */
private void processDownload(String url, long length, DownLoadCallBack callBack,List<DownloadEntity> cache) {
if(cache == null || cache.size() == 0) {
mCache = new ArrayList<>();
}
// 计算每一个线程下载的大小
long threadDownloadSize = length / MAX_THREAD;
// 分配每一个线程下载
for(int i = 0; i < MAX_THREAD; i++) {
// 计算每一个线程从多少下载 比如长度100 2个线程 0-49 50-99, 下面是计算的算法
long startSize = i * threadDownloadSize;
long endSize = 0;
if (endSize == MAX_THREAD - 1) {
endSize = length - 1;
} else {
endSize = (i + 1) * threadDownloadSize - 1;
}
// 创建实体,保存到数据库
DownloadEntity entity = new DownloadEntity();
entity.setDownload_url(url);
entity.setEnd_position(endSize);
entity.setStart_position(startSize);
entity.setThread_id(i + 1);
// 执行下载
sThreadPool.execute(new DownloadRunnable(startSize, endSize, url, callBack, entity));
}
}
在看下线程里面的操作:
public class DownloadRunnable implements Runnable{
/** 指定下载开始位置*/
private long mStart;
/** 指定下载结束位置*/
private long mEnd;
/** 请求url*/
private String mUrl;
/** 结果回调*/
private DownLoadCallBack mCallBack;
/** 需要保存数据的实体信息类*/
private DownloadEntity mEntity;
public DownloadRunnable(long mStart, long mEnd, String mUrl, DownLoadCallBack mCallBack, DownloadEntity entity) {
this.mStart = mStart;
this.mEnd = mEnd;
this.mUrl = mUrl;
this.mCallBack = mCallBack;
this.mEntity = entity;
}
@Override
public void run() {
// 下载完成后返回的结果 response
Response response = HttpManager.getInstance().syncRequestByRange(mUrl, mStart, mEnd);
if(response == null && mCallBack != null) {
mCallBack.fail(HttpManager.NETWORK_ERROR_CODE, "网络出问题了");
return;
}
// 获取本地下载储存的文件
File file = FlieStorageManager.getInstance().getFileByName(mUrl);
// 用处是,如果我断网或者暂停后,杀死App,重新进来,就根据此字段取进度
long finshProgress = mEntity.getProgress_position() == null ? 0 : mEntity.getProgress_position();
// 记录线程下载的进度
long progress = 0;
// 多个线程对文件指定的位置写入数据(因为是多线程下载,多个线程肯定会对一个文件可读 可写 可修改)
try {
// 参数1:指定操作的文件 参数2:可读 可写 可修改
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd");
// 指定一个偏移,下载的起始位置
randomAccessFile.seek(mStart);
byte[] buffer = new byte[1024];
int len = 0;
InputStream inputStream = response.body().byteStream();
// 读取返回来的数据, 写入本地文件中
while((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
randomAccessFile.write(buffer, 0, len);
// 更新数据库的进度
progress += len;
mEntity.setProgress_position(progress);
}
// 数据库取得数据 + 杀死App进来后记录的 == 一直没有退出应用的进度
mEntity.setProgress_position(mEntity.getProgress_position() + finshProgress);
randomAccessFile.close();
// 下载成功
mCallBack.success(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 下载的数据保存到数据库
DownloadHelper.getInstance().insert(mEntity);
}
}
}
然后再项目的Applaction中初始化数据库
public class MyApplacation extends Application {
@Override
public void onCreate() {
super.onCreate();
FlieStorageManager.getInstance().init(this);
// 初始化数据库
DownloadHelper.getInstance().init(this);
}
}
下载Apk案例:
private void multipleDownFileImage() {
// DownloadManager.getInstance().downLoad(“http://szimg.mukewang.com/5763765d0001352105400300-360-202.jpg“, new DownLoadCallBack() {
DownloadManager.getInstance().downLoad(“http://shouji.360tpcdn.com/160901/84c090897cbf0158b498da0f42f73308/com.icoolme.android.weather_2016090200.apk“, new DownLoadCallBack() {
@Override
public void success(final File file) {
// 目前成功回调两次,通过cout标识一下 暂时性解决一下
if(cout < 1) {
cout++;
return;
}
Log.e(“file”, “file success: ” + file.getAbsolutePath());
Log.e(“file”, “file : ” + file.length());
}
@Override
public void fail(int errorCode, String errorMessage) {
Log.e("file", "shibai");
}
@Override
public void progress(int progress) {
Log.e("file", "progress : " + progress);
mProgress.setProgress(progress);
}
});
}
OK,多线程下载APK案例,就说完了。看了这4篇文章,相信是可以学会的。
更多相关文章
- 充分利用 Java 的元数据,第 3 部分:高级处理
- Java多线程六:线程优先级和yield()让步函数
- java多线程爬虫
- java 的 数据库连接测试类 (SQL server)
- 如何将mysql中的数据插入组合框中?
- jsoup 分页抓取网页数据Java HTML Parser
- javafx 和netty 混合使用出现线程不一致问题,求大神指点
- Java中的数据类型
- 在文件中添加新数据后,JComboBox不会刷新