android retrofit download progress

前端 未结 3 647
傲寒
傲寒 2020-12-08 16:50

I\'m new to retrofit. I\'ve searched but didn\'t found a simple answer. I want to know how can I show progress of download in Notification bar or at least show a progress di

相关标签:
3条回答
  • 2020-12-08 17:29

    you can take a look here, you dont have to implement it by yourself ,the idea behind is to take the content-length of the request and when you write on the buffer just calculate your progress

    0 讨论(0)
  • 2020-12-08 17:44

    You need to create a specific OkHttp client which will intercept the network requests and send updates. This client should only be used for downloads.

    First you are going to need an interface, like this one:

    public interface OnAttachmentDownloadListener {
        void onAttachmentDownloadedSuccess();
        void onAttachmentDownloadedError();
        void onAttachmentDownloadedFinished();
        void onAttachmentDownloadUpdate(int percent);
    }
    

    Your download call should return a ResponseBody, which we will extend from to be able to get the download progress.

    private static class ProgressResponseBody extends ResponseBody {
    
        private final ResponseBody responseBody;
        private final OnAttachmentDownloadListener progressListener;
        private BufferedSource bufferedSource;
    
        public ProgressResponseBody(ResponseBody responseBody, OnAttachmentDownloadListener progressListener) {
            this.responseBody = responseBody;
            this.progressListener = progressListener;
        }
    
        @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 != -1 ? bytesRead : 0;
    
                    float percent = bytesRead == -1 ? 100f : (((float)totalBytesRead / (float) responseBody.contentLength()) * 100);
    
                    if(progressListener != null)
                        progressListener.onAttachmentDownloadUpdate((int)percent);
    
                    return bytesRead;
                }
            };
        }
    }
    

    Then you will need to create your OkHttpClient like this

    public OkHttpClient.Builder getOkHttpDownloadClientBuilder(OnAttachmentDownloadListener progressListener) {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
    
        // You might want to increase the timeout
        httpClientBuilder.connectTimeout(20, TimeUnit.SECONDS);
        httpClientBuilder.writeTimeout(0, TimeUnit.SECONDS);
        httpClientBuilder.readTimeout(5, TimeUnit.MINUTES);
    
        httpClientBuilder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                if(progressListener == null) return chain.proceed(chain.request());
    
            Response originalResponse = chain.proceed(chain.request());
            return originalResponse.newBuilder()
                    .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                    .build();
            }
        });
    
        return httpClientBuilder;
    }
    

    Finally you only have to create your Retrofit client a different way, by passing your new OkHttp client. Based on your code, you can use something like this:

     public Retrofit getDownloadRetrofit(OnAttachmentDownloadListener listener) {
    
        return new Retrofit.Builder()
                    .baseUrl("http://192.168.43.135/retro/") 
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(getOkHttpDownloadClientBuilder(listener).build())
                    .build();
    
    }
    

    Your listener will handle the creation of your notification or whatever else you want.

    0 讨论(0)
  • 2020-12-08 17:44

    Here is my variant with Kotlin's coroutines

    1. Specify API interface. We need @Streaming annotation to say Retrofit that we want to handle response body manually. Otherwise retrofit will try write your file straight into RAM
    interface Api {
    
        @Streaming
        @GET("get-zip-ulr/{id}")
        fun getZip(@Path("id") id: Int): Call<ResponseBody>
    }
    
    1. Create DataSource which will control downloading process
    class FilesDataSource(private val parentFolder: File, private val api: Api) {
    
        suspend fun downloadZip(id: Int, processCallback: (Long, Long) -> Unit): File {
            val response = api.getZip(id).awaitResponse()// returns the response, but it's content will be later
            val body = response.body()
            if (response.isSuccessful && body != null) {
                val file = File(parentFolder, "$id")
                body.byteStream().use { inputStream ->
                    FileOutputStream(file).use { outputStream ->
                        val data = ByteArray(8192)
                        var read: Int
                        var progress = 0L
                        val fileSize = body.contentLength()
                        while (inputStream.read(data).also { read = it } != -1) {
                            outputStream.write(data, 0, read)
                            progress += read
                            publishProgress(processCallback, progress, fileSize)
                        }
                        publishProgress(processCallback, fileSize, fileSize)
                    }
                }
                return file
            } else {
                throw HttpException(response)
            }
        }
    
        private suspend fun publishProgress(
            callback: (Long, Long) -> Unit,
            progress: Long, //bytes
            fileSize: Long  //bytes
        ) {
            withContext(Dispatchers.Main) { // invoke callback in UI thtread
                callback(progress, fileSize)
            }
        }
    }
    

    Now you can execute downloadZip() method in your ViewModel or Presenter and give it callback which will be linked to some ProgerssBar. After download completion you will receive downloaded file.

    0 讨论(0)
提交回复
热议问题