美文网首页Android精选
【Android架构】基于MVP模式的Retrofit2+RXj

【Android架构】基于MVP模式的Retrofit2+RXj

作者: 欢子3824 | 来源:发表于2018-12-05 18:09 被阅读150次

    前言:最近有个断点下载的需求,捣鼓了下,然后分享下

    关于文件下载,在第2篇中已经详细说过,这里就不在详细说了,先对之前的下载做个封装
    首先是ApiServer

       @Streaming
       @GET
       /**
        * 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入
        */
       Observable<ResponseBody> downloadFile(@Url String fileUrl);
    

    定义下载回调

    public interface DownFileCallback {
    
        void onSuccess(String path);
    
        void onFail(String msg);
    
        void onProgress(long totalSize, long downSize);
    }
    

    DownLoadManager

    public class DownLoadManager {
    
        private static DownLoadManager loadManager;
    
        private HashMap<String, FileObserver> hashMap;
        private OkHttpClient client;
        private Retrofit retrofit;
        private ApiServer apiServer;
        private DownFileCallback fileCallback;
       
    
    
        public DownLoadManager() {
            hashMap = new HashMap<>();
            client = new OkHttpClient.Builder()
                    .addNetworkInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
    
                            Request request = chain.request();
     
                           Response response = chain.proceed(request);
                            return response.newBuilder().body(new ProgressResponseBody(response.body(),
                                    new ProgressResponseBody.ProgressListener() {
                                        @Override
                                        public void onProgress(long totalSize, long downSize) {
                                            if (fileCallback != null) {
                                                fileCallback.onProgress(totalSize, downSize);
                                            }
                                        }
                                    })).build();
                        }
                    }).build();
            retrofit = new Retrofit.Builder().client(client)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .baseUrl("https://wawa-api.vchangyi.com/").build();
            apiServer = retrofit.create(ApiServer.class);
        }
    
        public static DownLoadManager getInstance() {
            synchronized (Object.class) {
                if (loadManager == null) {
                    loadManager = new DownLoadManager();
                }
            }
            return loadManager;
        }
    
     /**
         * 下载单个文件
         *
         * @param url
         * @param fileCallback
         */
        public void downFile(final String url, final DownFileCallback fileCallback) {
            //如果正在下载,则暂停
            if (isDownLoad(url)) {
                pause(url);
                return;
            }
            this.fileCallback = fileCallback;
            //存储的文件路径
            final String path = getTemporaryName(url);
    
            FileObserver observer = apiServer.downloadFile(url).map(new Function<ResponseBody, String>() {
                @Override
                public String apply(ResponseBody body) throws Exception {
                    File file = FileUtil.saveFile(path, body);
                    return file.getPath();
                }
            }).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeWith(new FileObserver<String>() {
                        @Override
                        public void onSuccess(String path) {
    
                            fileCallback.onSuccess(path);
                            hashMap.remove(url);
                        }
    
                        @Override
                        public void onError(String msg) {
                            fileCallback.onFail(msg);
                            hashMap.remove(url);
                        }
                    });
            //保存
            hashMap.put(url, observer);
        }
     /**
         * 暂停/取消任务
         *
         * @param url 完整url
         */
        public void pause(String url) {
            if (hashMap.containsKey(url)) {
                FileObserver observer = hashMap.get(url);
                if (observer != null) {
                    observer.dispose();
                    hashMap.remove(url);
                }
            }
        }
    
        /**
         * 获取临时文件名
         *
         * @param url
         * @return
         */
        public static String getTemporaryName(String url) {
            String type = "";
            if (url.contains(".")) {
                type = url.substring(url.lastIndexOf("."));
            }
            String dirName = Environment.getExternalStorageDirectory() + "/mvp/";
    
            File f = new File(dirName);
            //不存在创建
            if (!f.exists()) {
                f.mkdirs();
            }
            return dirName + System.currentTimeMillis() + type;
        }
    
    
        /**
         * 是否在下载
         *
         * @param url
         * @return
         */
        public boolean isDownLoad(String url) {
            return hashMap.containsKey(url);
        }
    
    
        public abstract class FileObserver<T> extends DisposableObserver<T> {
    
            @Override
            public void onNext(T t) {
                onSuccess(t);
            }
    
            @Override
            public void onError(Throwable e) {
                onError(e.getMessage());
            }
    
            @Override
            public void onComplete() {
    
            }
    
    
            public abstract void onSuccess(T o);
    
            public abstract void onError(String msg);
        }
    }
    

    断点下载的话,有2点要注意

    • 1.要添加RANGE请求头
    if (downSize != 0 && totalSize != 0) {
                                request = request.newBuilder()
                                        .addHeader("RANGE", "bytes=" + downSize + "-" + totalSize).build();
                            }
    

    downSize 和totalSize ,前者是下载的开始长度,后者是整个文件的长度,这些都需要我们暂停时,自己做保存。

    • 2.写文件问题,断点下载时,就不能从头开始写入文件,需要从上次结束的地方开始,这里就用到了RandomAccessFile
      /**
         * @param filePath
         * @param start    起始位置
         * @param body
         */
        public static File saveFile(String filePath, long start, ResponseBody body) {
            InputStream inputStream = null;
            RandomAccessFile raf = null;
            File file = null;
            try {
                file = new File(filePath);
    
                raf = new RandomAccessFile(filePath, "rw");
                inputStream = body.byteStream();
                byte[] fileReader = new byte[4096];
                //移动到该位置
                raf.seek(start);
    
                while (true) {
                    int read = inputStream.read(fileReader);
                    if (read == -1) {
                        break;
                    }
                    raf.write(fileReader, 0, read);
                }
    
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            return file;
    
        }
    

    建个实体类,来保存文件相关属性,get和set就不贴了

    public class DownModel {
    
        private String url;
        private String path;
        private String title;
        private String cover;
        private long totalSize;
        private long currentTotalSize;
        private long downSize;
        private boolean isExists;
        private boolean isFinish;
        private boolean isPause;
    }
    
     /**
         * 下载单个文件
         *
         * @param downModel
         * @param
         */
        public void downFile(final DownModel downModel, final DownFileCallback fileCallback) {
            if (downModel == null) {
                return;
            }
            //如果正在下载,则暂停
            final String url = downModel.getUrl();
            if (isDownLoad(url)) {
                pause(url);
                Log.e("cheng", "pause url=" + url);
                return;
            }
            //当前链接
            currentUrl = url;
            //是否是断点下载
            if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
                totalSize = downModel.getTotalSize();
                downSize = downModel.getDownSize();
                currentPath = downModel.getPath();
            } else {
                totalSize = 0;
                downSize = 0;
                currentPath = getTemporaryName(url);
                downModel.setPath(currentPath);
            }
    
            this.fileCallback = fileCallback;
    
            Log.e("cheng", "currentUrl=" + currentUrl);
    
            Log.e("cheng", "downSize=" + downSize + ",totalSize=" + totalSize + ",currentPath=" + currentPath);
    
            FileObserver observer = apiServer.downloadFile(url).map(new Function<ResponseBody, String>() {
                @Override
                public String apply(ResponseBody body) throws Exception {
    
                    if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
                        return FileUtil.saveFile(currentPath, downModel.getDownSize(), body).getPath();
                    }
                    File file = FileUtil.saveFile(currentPath, body);
                    return file.getPath();
                }
            }).subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeWith(new FileObserver<String>() {
                        @Override
                        public void onSuccess(String path) {
                            downModel.setFinish(true);
                            downModel.setPath(path);
                            downModel.setExists(true);
    
    
                            if (fileCallback != null) {
                                fileCallback.onSuccess(path);
                            }
                            hashMap.remove(url);
    
                            currentUrl = null;
                        }
    
                        @Override
                        public void onError(String msg) {
                            if (fileCallback != null) {
                                fileCallback.onFail(msg);
                            }
                            hashMap.remove(url);
                            currentUrl = null;
                        }
                    });
            //保存
            hashMap.put(url, observer);
        }
    

    需要注意的是,如果文件总大小为50M,已下载的大小为10M,再次下载时onProgress返回的totalSize是文件总长度 减去 已下载大小 10M, 即40M,downSize为本次下载的已下载量

     private void down2() {
           String url = "http://download.sdk.mob.com/apkbus.apk";
           if (downModel == null) {
               downModel = new DownModel();
               downModel.setUrl(url);
           }
           DownLoadManager.getInstance().downFile(downModel, new DownFileCallback() {
               @Override
               public void onSuccess(String path) {
                   showtoast("下载成功,path=" + path);
               }
    
               @Override
               public void onFail(String msg) {
    
               }
    
               @Override
               public void onProgress(long totalSize, long downSize) {
                   Log.e("cheng", "totalSize =" + totalSize + ",downSize=" + downSize);
                   if (downModel.getTotalSize() == 0) {
                       downModel.setTotalSize(totalSize);
                   }
                   downModel.setCurrentTotalSize(totalSize);
    
                   downModel.setDownSize(downSize + downModel.getTotalSize() - downModel.getCurrentTotalSize());
    
                   runOnUiThread(new Runnable() {
                       @Override
                       public void run() {
                           int progress = (int) (downModel.getDownSize() * 100 / downModel.getTotalSize());
                           tvDown.setText(progress + "%");
                           sbDown.setProgress(progress);
                       }
                   });
    
    
               }
           });
       }
    

    最后,献上源码 Github

    你的认可,是我坚持更新博客的动力,如果觉得有用,就请点个赞,谢谢

    相关文章

      网友评论

        本文标题:【Android架构】基于MVP模式的Retrofit2+RXj

        本文链接:https://www.haomeiwen.com/subject/qkuwcqtx.html