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

作者: 凌音同学 | 来源:发表于2017-01-04 22:37 被阅读0次

    日常扯蛋

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

    不管怎样,都要谢谢你鼓起勇气跟我说这些,能被人喜欢是件好事情,还蛮开心的,不过我已经有喜欢的人了,他不在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.haomeiwen.com/subject/vrghmttx.html