自己造轮子,写个下载管理器(一)

日常扯蛋

从小到大都是被追求或是互相默许,前两天第一次像个初中生,鼓起勇气的,用微信对她承认了“我的确是喜欢你!”,但她的回答却是。。。

不管怎样,都要谢谢你鼓起勇气跟我说这些,能被人喜欢是件好事情,还蛮开心的,不过我已经有喜欢的人了,他不在XX,分享的像个挺好的,保持呗。我一直都挺看好你的,希望你可以好好加油,一定更适合你的女生。祝君好。

得到这回答的我当然是悲喜交加。在后续的对话中,明白了,她不是完全拒绝我,我还是有希望的。这听起来好像是很犯贱的想法,可是现实就是这样,没多少情侣是一蹴而就的,更多的,是需要努力,一个人,或者两个人的努力。用鸡汤文来说就是:

人生会经历三次成长:

  • 第一次是发现自己不是世界中心;
  • 第二次是发现即使再怎么努力,终究还是有些事令人无能为力;
  • 第三次是在明知道有些事可能会无能为力,但还是会尽力争取的时候。

我想我会努力的,就算没有结果,也不会放弃。感情就是这样,只要你喜欢,没有值不值得。;)

正题

项目需求总在变,不变的只有我们苦命的码农 ;(。因为客户天天催促着上线,又要求App必须要强制升级(公司业务App,不是对外的,所以和我码农底线无关),我原本打算封装一个下载管理器,来负责这个项目的APK下载,但由于时间问题,没有时间再设计、测试,所以只能宣布夭折,然后针对当前这个项目,写一个DownloadService下载。但客户又说要能够做到断点续传、联网自动继续下载(App打开情况下)、下载完成后没安装,退出App再打开,没有联网的情况下继续弹出安装的界面……(听到这些需求的我当时已经崩溃),花了两三天,赶出来测试、上线,到下载版本推送的时候,就尴尬了,由于下载网络的问题,各种错误,重复添加下载任务、下载无进度…………经理也无奈了,因为他也测试了这些case,为什么没有发现问题。。。。那天加班到凌晨3点,总算解决,但只是用临时方法,不能长久这样。所以我回头又想起这个之前我打算封装的模块DownloadManager。有人问,为什么不用系统自带的DownloadManager,我想说的是“对啊,为啥,我也不知道,就是不想用,就是偏执于自己造轮子(好吧,主要就是系统的这玩意儿你不好控制)”。就趁这段时间闲着,开艹代码吧。

开源库

开源库肯定是要用到的,大路不走偏挑山路,不是浪费生命吗。但不是直接找一个开源的DownloadManager,因为你自己完全有能力写一个,而且也不难(Glide、Rxjava什么的就算了,那个有点难,还是不自己造轮子了)。我们主要用到两个:

  • OkHttp3
  • GreenDao 3.1
  • Gson

OkHttp3就不用说了吧,当今主流,主要就是解决网络请求问题。个人感觉,这玩意儿会火,不是因为入门简单,比它简单的有Volley,那为什么呢?我觉得应该就是定制性。可定制极高,接口设计很精髓,属于诗歌级别的代码。
GreenDao负责下载任务的DB操作。它倒是没有OkHttp那么出名。但这不是问题,它的实力摆在那里,这里就不吹嘘它了,仁者见仁智者见智。
Gson不多说,大家都懂。好吧,这个管理器其实可以不用这玩意儿(也就一个地方用到,而且还是无关紧要),但是,我还是偷懒了,有时间再去掉吧。

可能有的人会说,“为什么不用Retrofit2RxJava等开源库呢?”。其实吧,我在第一个版本里就是用了这些库,但在后面改善、优化的时候意识到,我们应该尽量在封装的库里,少使用第三方的其它库,可以尽量减少耦合度,减少后续维护的成本。

基本流程图

(明天补上,下载Visio中)

开始网络请求

首先我们先构建个负责构建CallNetKit.java

/**
 * Created by mid1529 on 2016/12/23.
 * 网络请求
 */

public class NetKit {
    private static final long TIMEOUT = 100000;
    private static NetKit mNetKit = null;
    private static OkHttpClient mClient = null;

    private NetKit() {
        initClient();
    }

    /**
     * 单例模式,保证唯一
     *
     * @return Netkit
     */
    public static NetKit getKit() {
        if (mNetKit == null) {
            synchronized (NetKit.class) {
                if (null == mNetKit) {
                    mNetKit = new NetKit();
                }
            }
        }
        return mNetKit;
    }

    /**
     * 得到OkhttpClient实例
     *
     * @return Client
     */
    private OkHttpClient initClient() {
        if (mClient == null) {
            mClient = new OkHttpClient.Builder()
                    .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                    .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                    .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                    .addNetworkInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Response originalResponse = chain.proceed(chain.request());
                            return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body())).build();
                        }
                    })
                    .build();
        }
        return mClient;
    }

    /**
     * 获取下载实例Call
     *
     * @param url     网址
     * @param headers 头,可为空
     * @return Call
     */
    public synchronized Call get(@NonNull String url, @Nullable Headers headers) {
        Request.Builder builder = new Request.Builder()
                .url(url)
                .get();
        if (headers != null) {
            builder.headers(headers);
        }
        return mClient.newCall(builder.build()).clone();
    }
}

注意get(@NonNull String url, @Nullable Headers headers)这个方法,return的是Call,所以这里只是构建出Call,而不是真正的请求。然后下载通常是用Get的方式,所以用Get,参数第一个代表URL,第二个么,代表请求头,因为考虑到,有的下载服务器可能需要头验证,所以预留,但这里已经通过注解声明了@Nullable,所以可以直接传null

建立下载任务实体

下载任务实体命名就俗套一点,叫做DownloadTask。但在这里,我在看其它开源的下载的时候,发现有的把下载文件读写操作等写在了这实体里,执行时,相当于下载任务本身完成了自己本身的下载任务,虽然没毛病,但总感觉怪怪的,所以我这里觉得,实体只是记录信息,而不包括下载、读写等。废话不说,看代码DownloadTask.java

  @Entity  //GreenDao的注解,下面也是,不懂的可以Google了解
public class DownloadTask {
    @Id(autoincrement = true)
    private Long taskId;    //下载的Id,数据库自动生成
    @Unique
    @NotNull
    private String url; //下载的URL
    @Unique
    @NotNull
    private String fileName;    //下载的文件名
    @NotNull
    private boolean isAutoContinue = false;
    @Convert(converter = DownloadPropertyConverter.class, columnType = String.class)  //参数转换器,将Map对象转为`String`储存,查询时,又把String转为Map
    private Map<String, String> headers = null;  //请求时的Header
    @Transient
    private long contentLength;  //下载文件的总长度
    @Transient
    private long downloadedLength;  //下载文件已下载长度
    @Transient
    private boolean isResumeBrokenTransfer = true;  //是否开启断点续传,默认开启
    @Transient
    private boolean isPause = false;    //是否为暂停,默认为false,防止暂停后,会走onFaild回调方法
    @Transient
    private Call call = null;
    @Transient
    private Object tag; //预留,参考View的Tag
    public String downloadPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/"; //下载目录
    @Transient
    private File file = null;
    
//****省略各种getter、setter

}

转化器DownloadPropertyConverter.java

  /**
 * Created by mid1529 on 2016/12/26.
 * 保留下载任务持久化 时的Headers数据的Converter
 * 唯一用到Gson的地方,哈哈哈哈
 */

public class DownloadPropertyConverter implements PropertyConverter<Map<String, String>, String> {

    @Override
    public Map<String, String> convertToEntityProperty(String databaseValue) {
        return new Gson().fromJson(databaseValue, new TypeToken<Map<String, String>>() {
        }.getType());
    }

    @Override
    public String convertToDatabaseValue(Map<String, String> entityProperty) {
        return new Gson().toJson(entityProperty);
    }
}

先写到这,过两天继续

明天继续DownloadService和负责IO读写的东西等,好累。

    原文作者:凌音同学
    原文地址: https://www.jianshu.com/p/6f1af4ffde33
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞