【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件下载(二)

栏目: Java · 发布时间: 5年前

内容简介:上篇中我们介绍了基于MVP的Retrofit2+RXjava封装,这一篇我们来说说文件下载的实现。我们先在ApiServer定义好调用的接口接着定义一个接口,下载成功后用来回调

上篇中我们介绍了基于MVP的Retrofit2+RXjava封装,这一篇我们来说说文件下载的实现。

我们先在ApiServer定义好调用的接口

@GET
    Observable<ResponseBody> downloadFile(@Url String fileUrl);
复制代码

接着定义一个接口,下载成功后用来回调

public interface FileView extends BaseView {

    void onSuccess(File file);
}
复制代码

接着是Observer,建议与处理普通接口的Observer区分处理

public abstract class FileObsever extends BaseObserver<ResponseBody> {
    private String path;

    public FileObsever(BaseView view, String path) {
        super(view);
        this.path = path;
    }

    @Override
    protected void onStart() {
    }

    @Override
    public void onComplete() {
    }

    @Override
    public void onSuccess(ResponseBody o) {

    }

    @Override
    public void onError(String msg) {

    }

    @Override
    public void onNext(ResponseBody o) {
        File file = FileUtil.saveFile(path, o);
        if (file != null && file.exists()) {
            onSuccess(file);
        } else {
            onErrorMsg("file is null or file not exists");
        }
    }

    @Override
    public void onError(Throwable e) {
        onErrorMsg(e.toString());
    }


    public abstract void onSuccess(File file);

    public abstract void onErrorMsg(String msg);
}

复制代码

FileUtil 注:如果需要写入文件的进度,可以在将这段方法放在onNext中,在FileObsever这个类写个方法,然后回调。

public static File saveFile(String filePath, ResponseBody body) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        File file = null;
        try {
            if (filePath == null) {
                return null;
            }
            file = new File(filePath);
            if (file == null || !file.exists()) {
                file.createNewFile();
            }


            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;
            byte[] fileReader = new byte[4096];

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(file);

            while (true) {
                int read = inputStream.read(fileReader);
                if (read == -1) {
                    break;
                }
                outputStream.write(fileReader, 0, read);
                fileSizeDownloaded += read;

            }

            outputStream.flush();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return file;
    }
复制代码

下来是FilePresenter

public class FilePresenter extends BasePresenter<FileView> {
    public FilePresenter(FileView baseView) {
        super(baseView);
    }

    public void downFile(String url, final String path) {

        addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) {


            @Override
            public void onSuccess(File file) {
                if (file != null && file.exists()) {
                    baseView.onSuccess(file);
                } else {
                    baseView.showError("file is null");
                }
            }

            @Override
            public void onErrorMsg(String msg) {
                baseView.showError(msg);

            }
        });

    }
    }
复制代码

最后在Activity中调用

private void downFile() {
        String url = "http://download.sdk.mob.com/apkbus.apk";

        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {// 检查是否有存储卡
            dir = Environment.getExternalStorageDirectory() + "/ceshi/";
            File dirFile = new File(dir);
            if (!dirFile.exists()) {
                dirFile.mkdirs();
            }
        }
        presenter.downFile(url, dir + "app-debug.apk");
    }
复制代码

就在我以为万事大吉的时候,APP崩溃了,错误信息如下: [图片上传失败...(image-39e2a7-1532052138950)]

原来是加入日志监听器,会导致每次都把整个文件加载到内存,那我们就去掉这个

修改FilePresenter#downFile如下:

public void downFile(String url, final String path) {

        OkHttpClient client = new OkHttpClient.Builder().build();
        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);

        addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) {
            @Override
            public void onSuccess(File file) {
                if (file != null && file.exists()) {
                    baseView.onSuccess(file);
                } else {
                    baseView.showError("file is null");
                }
            }

            @Override
            public void onErrorMsg(String msg) {
                baseView.showError(msg);

            }
        });
    }
复制代码

这次倒是下载成功了,不过官方建议10M以上的文件用Streaming标签,我们加上Streaming标签试试 修改ApiServer

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

这次又崩溃了,错误信息如下: [图片上传失败...(image-fc8c28-1532052138951)]

这是怎么回事,我们网络请求是在子线程啊。无奈之下只得翻翻官方文档,原来使用该注解表示响应用字节流的形式返回.如果没使用该注解,默认会把数据全部载入到内存中。我们可以在主线程中处理写入文件(不建议),但不能在主线程中处理字节流。所以,我们需要将处理字节流、写入文件都放在子线程中。

于是,修改FilePresenter#downFile如下:

OkHttpClient client = new OkHttpClient.Builder().build();
        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);
          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(baseView) {
                    @Override
                    public void onSuccess(File file) {
                        baseView.onSuccess(file);
                    }

                    @Override
                    public void onError(String msg) {
                        baseView.showError(msg);
                    }
                });
复制代码

这样,下载文件算是完成了,好像还缺点什么?对,缺个下载进度,还记得拦截器吗,我们可以从这里入手:

public class ProgressResponseBody extends ResponseBody {
    private ResponseBody responseBody;
    private BufferedSource bufferedSource;
    private ProgressListener progressListener;

    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }


    @Nullable
    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    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);
                totalBytesRead += bytesRead;
                progressListener.onProgress(responseBody.contentLength(), totalBytesRead);
                return bytesRead;
            }
        };
    }

    public interface ProgressListener {
        void onProgress(long totalSize, long downSize);
    }
}

复制代码

在BaseView 中定义接口,个人建议放在BaseView 中,在BaseActivity中实现BaseView,方便复用

/**
     * 下载进度
     *
     * @param totalSize
     * @param downSize
     */

    void onProgress(long totalSize, long downSize);
复制代码

再次修改FilePresenter#downFile如下:

public void downFile(final String url, final String path) {


        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Response response = chain.proceed(chain.request());
                        return response.newBuilder().body(new ProgressResponseBody(response.body(),
                                new ProgressResponseBody.ProgressListener() {
                                    @Override
                                    public void onProgress(long totalSize, long downSize) {
                                        baseView.onProgress(totalSize, downSize);
                                    }
                                })).build();
                    }
                }).build();

        Retrofit retrofit = new Retrofit.Builder().client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl("https://wawa-api.vchangyi.com/").build();

        apiServer = retrofit.create(ApiServer.class);

        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(baseView) {
                    @Override
                    public void onSuccess(File file) {
                        baseView.onSuccess(file);
                    }

                    @Override
                    public void onError(String msg) {
                        baseView.showError(msg);
                    }
                });


    }
复制代码

至此,使用Retrofit下载文件暂时告一段落。


以上所述就是小编给大家介绍的《【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件下载(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript & jQuery

JavaScript & jQuery

David Sawyer McFarland / O Reilly / 2011-10-28 / USD 39.99

You don't need programming experience to add interactive and visual effects to your web pages with JavaScript. This Missing Manual shows you how the jQuery library makes JavaScript programming fun, ea......一起来看看 《JavaScript & jQuery》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试