欢迎转载,也请保留这段申明 ,原文地址:https://blog.csdn.net/u011418943/article/details/85760069
开发中,我们常常会需要有apk升级,或者下载某个文件的问题。所以这里就写了个通用的文件下载的功能 ZDloader。通过这篇文章你将看到
- 常用框架 API 接口设计
- 多线程下载原理与实现
- 后台下载,界面退出之后,进来继续显示下载UI的原理
工程链接如下:https://github.com/LillteZheng/ZDownLoader
1、先看下载效果:
2、配置
你的时间非常宝贵,跟我一起…,呸 ,来看看 ZDloader 怎么关联吧!
先写上 jitpack
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
然后把 ZDloader 写上:
implementation 'com.github.LillteZheng:ZDownLoader:1.3'
ZDloader 的下载配置非常简单:
//如果不是正在下载,则让它继续下载即可
if (!ZDloader.isDownloading()) {
ZDloader.with(MainActivity.this)
.url(URL)
//路径不写默认在Environment.getExternalStorageDirectory().getAbsolutePath()/ZDloader
.savePath(Environment.getExternalStorageDirectory().getAbsolutePath())
.fileName("test.apk") //文件名不写,则默认以链接的后缀名当名字
.threadCount(3) //线程个数
.reFreshTime(1000) //刷新时间,不低于200ms
.allowBackDownload(true) //是否允许后台下载
.listener(MainActivity.this)
.download();
}
ZDloader 为唯一暴露的接口,它提供以下方法:
void pauseDownload();//暂停
void reStartDownload(); //重新下载
void startDownload(); //下载
void deleteDownload(); //删除任务
boolean isDownloading(); //是否正在下载
其中 listener 那提供比较容易扩展的接口:
//下载成功,返回下载文件,由开发者自己定义功能
void onSuccess(String path);
//错误提示,返回错误类型和错误信息
void onError(NetErrorStatus errorStatus, String errorMsg);
//下载信息,ZDownlaodBean,会返回下载速度,下载的文件长度,和总的文件长度
void onDownloading(ZDownloadBean bean);
3、功能讲解
3.1、常用框架 API接口设计
在使用一些比较好用的框架,比如 glide ,它的接口设计是非常好用的,比如:
Glide.with(this)
.into(object);
所以,ZDloader 在设计的时候,也采用这种构建者模式,原理也不难,就是在常用的类中,先构建一个接口:
public static RequestManager with(Context context){
mRequestManager = new RequestManager().with(context);
//初始化数据库
ZDBManager.getInstance().config(context);
return new RequestManager().with(context);
}
在它的返回,则用 RequestManager 去管理拿到的对象,RequestManager 可以是一个 Builder 模式,这里我采用比较简单的 Builder 模式,
public RequestManager with(Context context) {
mInfo.context = context;
return this;
}
把 with 拿到的context,跟一个 LifeFragment 管理起来,这里我们就能拿到生命周期了
if (info.context instanceof FragmentActivity){
FragmentActivity activity = (FragmentActivity) info.context;
if (activity.isDestroyed()){
throw new IllegalArgumentException("You cannot start a load task for a destroyed activity");
}
//添加一个隐形的 fragment ,用来管理生命周期
Fragment lifeFramgnet = activity.getSupportFragmentManager().findFragmentByTag(info.url);
InvisiabelFragment fragment ;
if (lifeFramgnet != null){
fragment = (InvisiabelFragment) lifeFramgnet;
}else{
fragment = InvisiabelFragment.newInstance();
}
......
3.2 多线程原理
这个就有点老生常谈了,无非就是把拿到的连接,识别的长度,再把它分成不同等份,最后一个是除不尽的,所以要加 1,比如11M分5个线程下载,原理如下图:
blocksize = 11%5 == 0? 11/5:11%5+1;
//每一个线程要下载的大小
blocksize = filesize%threadcount == 0? filesize/threadcount : filesize/threadcount+1
因为要分段,所以 http 的 head 就要加 range 属性,由于用 retrofit ,所以可以这样写:
@Streaming
@GET
Call<ResponseBody> download(@Url String url, @Header("RANGE") String range);
文件流的保存写入,则使用 RandomAccessFIle
new RandomAccessFile(file, "rwd");
具体细节看源码;
3.3、退出之后进来继续下载
笔者遇到一个问题,就是界面退出之后,重新进来,因为重新初始化了,相当于重新起了一个任务,那么下次的内容跟这次肯定是不一样的。
而这个问题的关键就在于,我们需要对任务初始化一次,这样想的话,我们只需要把下载任务的初始化放在 service 的onCreate ,保证了实例之后一个,那么下次进来,我们就可以用 isDownload 来判断是否正在下载了,这样也解决了从后台进来,有两次任务,导致UI错乱问题;
当然这只是一个思路,欢迎各位提出建议。