简体   繁体   中英

RxJava, OkHttp, Okio, file downloader in Android

I am trying to convert this working file download code to Reactive. But got stuck, due to my little knowledge of RxJava. Could you help me to make it Reactive?

public void downloadFile(MessageComponent media) {
        Request request = new Request.Builder()
                .url(media.getMediaUrl())
                .build();

        Call call = http_client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Log.e(TAG, "Failed to execute " + request, e);
            }

            @Override
            public void onResponse(Response response) throws IOException {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
                String mimeType = MimeTypeMap.getFileExtensionFromUrl(media.getMediaUrl());
                File file = new File(helper.getTmpFolder() + "/" + helper.generateUniqueName() + "test." + mimeType);
                BufferedSink sink = Okio.buffer(Okio.sink(file));
                sink.writeAll(response.body().source());
                sink.close();
                Log.d(TAG, "downloadFileFromServer done: " + media.getMediaUrl());
            }
        });
    } 

This is what I have written so far, and its not getting any result or error:

public void downloadFile(MessageComponent media){
   Observable<String> downloadObservable = Observable.create(
            sub -> {
                Request request = new Request.Builder()
                        .url(media.getMediaUrl())
                        .build();
                Response response = null;
                try {
                    response = http_client.newCall(request).execute();
                    if (!response.isSuccessful()) new IOException();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                sub.onNext(response.toString());
            }
    );

    Subscriber<String> mySubscriber = new Subscriber<String>() {
        @Override
        public void onNext(String responseString) {
            Log.d(TAG, "works: " + responseString);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable e) {
            Log.e(TAG, e.getMessage(), e);
        }
    };
    downloadObservable
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(mySubscriber);
    mySubscriber.unsubscribe();

}

Your code have some errors, which may explain that the expected behaviours is not what you get.

Observable contract error

Reactive Extensions (RxJava is an implemtation of it) is based on this contract : you can be notified multiple times on onNext then, you will notified once (or never...) on an error OR on a completion.

onNext* (onComplete | onError)?

So, your Observable code can be rewriten to this, to emit the fact that your stream is in error or that it is completed.

Observable<String> downloadObservable = Observable.create(
        sub -> {
            Request request = new Request.Builder()
                    .url(media.getMediaUrl())
                    .build();
            Response response = null;
                response = http_client.newCall(request).execute();
                if (response.isSuccessful()) {
                    sub.onNext(response.toString());
                    sub.onCompleted();
                } else {
                    sub.onError(new IOException());
                }
        }
);

Unsubscribe to earlier

You unsubscribe just after your subscription, so your Observable may not have the time to be executed.

downloadObservable
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(mySubscriber);
mySubscriber.unsubscribe();

If the Observable complete, it will unsubscribe. So won't have to unsubscribe in this case.

This question has the same final goal as the following, which is to download a file and save it to disk in Android:

Downloading files using OkHttp, Okio and RxJava

Download and write a file with Retrofit and RxJava

Here is a good example for downloading a file and save it to disk in Android.

The following is a modification of the above linked example without using the lambda expression.

1.Make sure to include the necessary dependencies in your app build gradle

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'io.reactivex:rxjava:1.1.5'
compile 'io.reactivex:rxandroid:1.1.0'

2.The Retrofit 2 interface, the @Streaming for downloading large files.

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

3.The code for downloading a file and saving it to disk using Retrofit 2 and rxjava. Update the baseUrl and the url path in the code below to your actual url of the file you need to download.

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());
                            }
                       });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM