撸一个自己的文件下载器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011418943/article/details/85760069

欢迎转载,也请保留这段申明 ,原文地址: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错乱问题;
当然这只是一个思路,欢迎各位提出建议。

猜你喜欢

转载自blog.csdn.net/u011418943/article/details/85760069