【问题标题】:Download and write a file with Retrofit and RxJava使用 Retrofit 和 RxJava 下载并编写文件
【发布时间】:2015-09-16 13:44:12
【问题描述】:

我正在下载一个带有改造的 pdf 文件,我下载它的方式是按块下载。我使用Content-Range 标头来获取一系列字节,然后我需要将这些字节写入file 问题是写入它们的顺序。我正在使用 flatMap() 函数为下载文件必须完成的每个请求返回一个 observable。

.flatMap(new Func1<Integer, Observable<Response>>() {
                @Override
                public Observable<Response> call(Integer offset) {
                    int end;

                    if (offset + BLOCK_SIZE > (contentLength - 1))
                        end = (int) contentLength - 1 - offset;

                    else
                        end = offset + BLOCK_SIZE;

                    String range = getResources().getString(R.string.range_format, offset, end);

                   return ApiAdapter.getApiService().downloadPDFBlock(range);
                }
            })

downloadPDFBlock 接收标头所需的字符串:Range: bytes=0-3999。然后我使用订阅函数来写入下载的字节

subscribe(new Subscriber<Response>() {
                @Override
                public void onCompleted() {
                    Log.i(LOG_TAG, file.getAbsolutePath());
                }

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

                @Override
                public void onNext(Response response) {
                    writeInCache(response);
                    }
                }
            });

但问题是编写过程是无序的。例如:如果首先下载Range: bytes=44959-53151,则这些字节将首先写入文件中。我已经阅读了关于BlockingObserver 的信息,但我不知道这是否是一个解决方案。

希望你能帮助我。

【问题讨论】:

  • 检查concatMap运营商fernandocejas.com/2015/01/11/…
  • 或者,使用 RandomAccessFile 并写入请求所针对的同一文件偏移量。
  • 您应该使用 DownloadManager 来加载大型内容。 developer.android.com/reference/android/app/…
  • @RalphBergmann 我需要分块下载,因为我正在使用 MuPDF 流式传输 PDF。因此,在下载文件时,我会在阅读器中显示页面。

标签: java android file-io retrofit rx-java


【解决方案1】:

这是一个很好的example,用于在 Android 中下载文件并将其保存到磁盘。

这是对上述链接示例的修改,不使用 lambda 表达式。

Retrofit 2 界面,用于下载大文件的@Streaming。

public interface RetrofitApi {
    @Streaming
    @GET
    Observable<Response<ResponseBody>> downloadFile(@Url String fileUrl);
}

使用 Retrofit 2 和 rxjava 下载文件并将其保存到磁盘的代码。将下面代码中的 baseUrl 和 url 路径更新为您需要下载的文件的实际 url。

public void downloadZipFile() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://my.resources.com/")
            .client(new OkHttpClient.Builder().build())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
    RetrofitApi downloadService = retrofit.create(RetrofitApi.class);

    downloadService.downloadFile("resources/archive/important_files.zip")
            .flatMap(new Func1<Response<ResponseBody>, Observable<File>>() {
                @Override
                public Observable<File> call(final Response<ResponseBody> responseBodyResponse) {
                    return Observable.create(new Observable.OnSubscribe<File>() {
                        @Override
                        public void call(Subscriber<? super File> subscriber) {
                            try {
                                // you can access headers of response
                                String header = responseBodyResponse.headers().get("Content-Disposition");
                                // this is specific case, it's up to you how you want to save your file
                                // if you are not downloading file from direct link, you might be lucky to obtain file name from header
                                String fileName = header.replace("attachment; filename=", "");
                                // will create file in global Music directory, can be any other directory, just don't forget to handle permissions
                                File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsoluteFile(), fileName);

                                BufferedSink sink = Okio.buffer(Okio.sink(file));
                                // you can access body of response
                                sink.writeAll(responseBodyResponse.body().source());
                                sink.close();
                                subscriber.onNext(file);
                                subscriber.onCompleted();
                            } catch (IOException e) {
                                e.printStackTrace();
                                subscriber.onError(e);
                            }
                        }
                    });
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<File>() {
                            @Override
                            public void onCompleted() {
                                Log.d("downloadZipFile", "onCompleted");
                            }

                            @Override
                            public void onError(Throwable e) {
                                e.printStackTrace();
                                Log.d("downloadZipFile", "Error " + e.getMessage());
                            }

                            @Override
                            public void onNext(File file) {
                                Log.d("downloadZipFile", "File downloaded to " + file.getAbsolutePath());
                            }
                       });
}

【讨论】:

  • 嘿伙计,您每次下载文件时都在创建服务器 api 实例
  • 这是为了演示,Retrofit 实例可以移动到全局状态。
  • 您已粘贴为参数@Url String fileUrl,但输入路径"resources/archive/important_files.zip"... 似乎方法应该是@Streaming @GET({path}) Observable&lt;Response&lt;ResponseBody&gt;&gt; downloadFile(@Path("path") String fileUrl);
  • 测试此解决方案的最佳方法是什么?似乎是紧密耦合(flatMap链式操作RxJava 与 Retrofit 紧密集成)。
  • 如何取得进展?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 1970-01-01
  • 2023-03-15
  • 2022-01-10
  • 2019-02-23
  • 2019-12-29
  • 1970-01-01
相关资源
最近更新 更多