RxJ2+Retrofit+OkHttp 学习分享(4)

最后是文件下载~~~
先给你一个盖帽 DownLaodActivity 类 重点代码以上

private void initResource(){
//DbDownUtil 新来的一个下载类
        dbUtil= DbDownUtil.getInstance();
        listData=dbUtil.queryDownAll();
        /*adapter数据填充的,其实也是最终会从数据库提出来的*/
        if(listData.isEmpty()){
//这里模拟的一个 数据集合下载用的地址
            String[] downUrl=new String[]{"http://www.izaodao.com/app/izaodao_app.apk",
                    "http://download.fir.im/v2/app/install/572eec6fe75e2d7a05000008?download_token=572bcb03dad2eed7c758670fd23b5ac4"};
            for (int i = 0; i < downUrl.length; i++) {
                File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                        "test"+i + ".apk");
//又一个心类,看参数,就应该知道,应该是一个 记录下载文件信息的类
                DownInfo apkApi=new DownInfo(downUrl[i]);
                apkApi.setId(i);
                apkApi.setState(DownState.START);
                apkApi.setSavePath(outputFile.getAbsolutePath());
                dbUtil.save(apkApi);
            }
       //  最后来个填充。。。然后。。。一个列表出来了
            listData=dbUtil.queryDownAll();
        }
    }
    /*加载控件 其实就是想,证明我上面说的 ,listData加数据的*/
    private void initWidget(){
        EasyRecyclerView recyclerView=(EasyRecyclerView)findViewById(R.id.rv);
        DownAdapter adapter=new DownAdapter(this);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
        adapter.addAll(listData);
    }

DbDownUtil主要代码

// 看过帖子1的,,是不是非常眼熟,,这个类,没错,就是一个文件操作类。操作数据库的。
public class DbDownUtil {
    private static DbDownUtil db;
    private final static String dbName = "tests_db";
    private DaoMaster.DevOpenHelper openHelper;
    private Context context;
.......都是增删改查的文件不重要,就是下载数据记录起来的。

DownInfo 下载文件信息 看见内容我就不用解释了吧GeenDao的操作实体对象类 @Transient 不存储在数据库中

@Entity
public class DownInfo{
    @Id
    private long id;
    /*存储位置*/
    private String savePath;
    /*文件总长度*/
    private long countLength;
    /*下载长度*/
    private long readLength;
    /*下载唯一的HttpService 其实url被观察者接口  重点-----重点*/
    @Transient  
    private HttpDownService service;
    /*回调监听 重点-----重点*/
    @Transient 
    private HttpDownOnNextListener listener;
    /*超时设置*/
    private  int connectonTime=6;
    /*state状态数据库保存*/
    private int stateInte;
    /*url*/
    private String url;

以上都是文件记录,并没有下载的方法,下载事件其实是在列表总。
DownAdapter->DownHolder(Holder我不介绍了吧,事件都在他这里)

//要看文件先看头 很明显 这里有“单击事件”
public class DownHolder extends BaseViewHolder<DownInfo> implements View.OnClickListener{
    private TextView tvMsg;
//进度条
    private NumberProgressBar progressBar;
//下载的文件信息
    private DownInfo apkApi;
//真正的下载处理方法
    private HttpDownManager manager;
//重点这这个方法 因为他一直在用 manager。。。
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_rx_down:
                if(apkApi.getState()!= DownState.FINISH){
                    manager.startDown(apkApi);
                }
                break;
            case R.id.btn_rx_pause:
                manager.pause(apkApi);
                break;
        }
    }

    /*下载回调 不用多解释了吧,最终的控制界面的*/
    HttpDownOnNextListener<DownInfo> httpProgressOnNextListener=new HttpDownOnNextListener<DownInfo>() {
        @Override
        public void onNext(DownInfo baseDownEntity) {
            tvMsg.setText("提示:下载完成");
            Toast.makeText(getContext(),baseDownEntity.getSavePath(),Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onStart() {
            tvMsg.setText("提示:开始下载");
        }

        @Override
        public void onComplete() {
            tvMsg.setText("提示:下载结束");
        }

        @Override
        public void onError(Throwable e) {
            super.onError(e);
            tvMsg.setText("失败:"+e.toString());
        }


        @Override
        public void onPuase() {
            super.onPuase();
            tvMsg.setText("提示:暂停");
        }

        @Override
        public void onStop() {
            super.onStop();
        }

        @Override
        public void updateProgress(long readLength, long countLength) {
            tvMsg.setText("提示:下载中");
            progressBar.setMax((int) countLength);
            progressBar.setProgress((int) readLength);
        }
    };


HttpDownManager 下载的操作方法

/*记录下载数据 set集合信息*/
    private Set<DownInfo> downInfos;
    /*回调sub队列*/
    private HashMap<String,ProgressDownSubscriber> subMap;
    /*单利对象*/
    private volatile static HttpDownManager INSTANCE;
    /*数据库类*/
    private DbDownUtil db;

// 重点代码。。 再来一次,下载配置。。。
/**
     * 开始下载
     */
    public void startDown(final DownInfo info){
        /*正在下载不处理*/
        if(info==null||subMap.get(info.getUrl())!=null){
            subMap.get(info.getUrl()).setDownInfo(info);
            return;
        }
        /*添加回调处理类 这个类就是一个观察者类,,最终的解释*/
        ProgressDownSubscriber subscriber=new ProgressDownSubscriber(info);
        /*记录回调sub*/
        subMap.put(info.getUrl(),subscriber);
        /*获取service,多次请求公用一个sercie*/
        HttpDownService httpService;
// 如果这个下载链接在原有的类表中已经存在了。把下载接口取出来。
        if(downInfos.contains(info)){
            httpService=info.getService();
        }else{
//创建一个下载拦截器 这个比较重要,进度,,就靠他了。
            DownloadInterceptor interceptor = new DownloadInterceptor(subscriber);
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            //手动创建一个OkHttpClient并设置超时时间
            builder.connectTimeout(info.getConnectonTime(), TimeUnit.SECONDS);
            builder.addInterceptor(interceptor);
// 链接OKhttp和rxjava的功臣
            Retrofit retrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(AppUtil.getBasUrl(info.getUrl()))
                    .build();
            httpService= retrofit.create(HttpDownService.class);
            info.setService(httpService);
//把新建的对象,,放到类表里 一方便下次使用
            downInfos.add(info);
        }
        /*得到rx对象-上一次下载的状态*/
        httpService.download("bytes=" + info.getReadLength() + "-",info.getUrl())
                /*指定线程*/
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                   /*失败后的retry配置*/
                .retryWhen(new RetryWhenNetworkException())
                /*读取下载写入文件*/
                .map(new Func1<ResponseBody, DownInfo>() {
                    @Override
                    public DownInfo call(ResponseBody responseBody) {
                        try {
                            AppUtil.writeCache(responseBody,new File(info.getSavePath()),info);
                        } catch (IOException e) {
                            /*失败抛出异常*/
                            throw new HttpTimeException(e.getMessage());
                        }
                        return info;
                    }
                })
                /*回调线程*/
                .observeOn(AndroidSchedulers.mainThread())
                /*数据回调*/
                .subscribe(subscriber);

    }

ProgressDownSubscriber

// 一个观察者,而且还有监听 其实这个监听就是Downinfo中的监听,在adapter中实现的。
public class ProgressDownSubscriber<T> extends Subscriber<T> implements DownloadProgressListener 
    //弱引用结果回调
    private SoftReference<HttpDownOnNextListener> mSubscriberOnNextListener;
    /*下载数据*/
    private DownInfo downInfo; 
// 其实是把监听接口转移出去 给DownloadProgressListener。
 @Override
    public void update(long read, long count, boolean done) {
        if(downInfo.getCountLength()>count){
            read=downInfo.getCountLength()-count+read;
        }else{
            downInfo.setCountLength(count);
        }
        downInfo.setReadLength(read);
        if (mSubscriberOnNextListener.get() != null) {
            /*接受进度消息,造成UI阻塞,如果不需要显示进度可去掉实现逻辑,减少压力*/
            rx.Observable.just(read).observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Action1<Long>() {
                        @Override
                        public void call(Long aLong) {
                      /*如果暂停或者停止状态延迟,不需要继续发送回调,影响显示*/
                            if(downInfo.getState()==DownState.PAUSE||downInfo.getState()==DownState.STOP)return;
                            downInfo.setState(DownState.DOWN);
                            mSubscriberOnNextListener.get().updateProgress(aLong,downInfo.getCountLength());
                        }
                    });
        }
    }

// 解释一下两者的关系,先会执行onNext接受数据,在会执行onCompleted 表示结束,可以在所以在前面的函数回调中可以看到的是 这两个方法都是一样内容显示
    public void onCompleted() 
    public void onNext(T t) 
...内容其实都是围绕,,观察者方法写的。

DownloadInterceptor

public class DownloadInterceptor implements Interceptor {
    private DownloadProgressListener listener;
    public DownloadInterceptor(DownloadProgressListener listener) {
        this.listener = listener;
    }
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
//刚才说的那个接口转换的过程在这里,是body要用
        return originalResponse.newBuilder()
                .body(new DownloadResponseBody(originalResponse.body(), listener))
                .build();
    }
}

DownloadResponseBody 核心代码

其实就是一个流文件的写入
private Source source(Source source) {
       return new ForwardingSource(source) {
           long totalBytesRead = 0L;

           @Override
           public long read(Buffer sink, long byteCount) throws IOException {
               long bytesRead = super.read(sink, byteCount);
               // read() returns the number of bytes read, or -1 if this source is exhausted.
               totalBytesRead += bytesRead != -1 ? bytesRead : 0;
               if (null != progressListener) {
                   progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
               }
               return bytesRead;
           }
       };

   }

AppUtil 真正的读写文件在这里,,HttpDownManager中的使用了它前面的都是数据

 /**
     * 写入文件
     * @param file
     * @param info
     * @throws IOException
     */
    public  static  void writeCache(ResponseBody responseBody, File file, DownInfo info) throws IOException{
        if (!file.getParentFile().exists())
            file.getParentFile().mkdirs();
        long allLength;
        if (info.getCountLength()==0){
            allLength=responseBody.contentLength();
        }else{
            allLength=info.getCountLength();
        }
        FileChannel channelOut = null;
// 可以控制文件指针的类 其实就是可以断点续传的原因
        RandomAccessFile randomAccessFile = null;
        randomAccessFile = new RandomAccessFile(file, "rwd");
        channelOut = randomAccessFile.getChannel();
//java大文件读写操作 用这个,效率高。百度的。。
        MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,
                info.getReadLength(),allLength-info.getReadLength());
        byte[] buffer = new byte[1024*8];
        int len;
        int record = 0;
        while ((len = responseBody.byteStream().read(buffer)) != -1) {
            mappedBuffer.put(buffer, 0, len);
            record += len;
        }
        responseBody.byteStream().close();
        if (channelOut != null) {
            channelOut.close();
        }
        if (randomAccessFile != null) {
            randomAccessFile.close();
        }
    }

全部分析完毕,简单,解释一下,文件拦截器是为了下载做的一个格式化。被监听着的时候就已经开始文件读写操作了。然后把最终的控制进度条的操作放在监听着中。

rxjava 被监听者做文件格式处理,监听者做最终的界面修改·~~
整合的整个类库已经全部搞定了。之后我想分析一下~OKGo类库,看着原作者分装的也是棒棒的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容