Reputation: 218
I'm downloading a pdf file with retrofit, the way that i'm downloading it is by blocks. I use the Content-Range
header to obtain a range of bytes, then i need to write these bytes on a file
the problem is the order to write them. I'm using the flatMap()
function to return an observable for each request that must be done to download the file.
.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);
}
})
The downloadPDFBlock
receive an strings that is needed by a header : Range: bytes=0-3999
. Then i use subscribe function to write the bytes downloaded
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);
}
}
});
But the problem is that the writing process is made unordered. For example: if the Range: bytes=44959-53151
is downloaded first, these will be the bytes that will be written first in the file. I have read about BlockingObserver
but i don't know if that could be a solution.
I hope you could help me.
Upvotes: 8
Views: 18545
Reputation: 25816
Here is a good example for downloading a file and save it to disk in Android.
Here is a modification of the above linked example without using the lambda expression.
The Retrofit 2 interface, the @Streaming for downloading large files.
public interface RetrofitApi {
@Streaming
@GET
Observable<Response<ResponseBody>> downloadFile(@Url String fileUrl);
}
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());
}
});
}
Upvotes: 15