Alexander
Alexander

Reputation: 1770

How to pause/resume downloading with okhttp in Android

I use okhttp library for download files in android. I download successfully. But something is wrong when I pause and resume download.

Response request = new Request.Builder().url(url).build();
ResponseBody responseBody = response.body();

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
    input.skip(downloadedSize);
} else {
    output = new FileOutputStream(file, false);    
}

long totalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;

while ((count = input.read(data)) != -1) {
    downloadedSize += count;
    output.write(data, 0, count);   
}

The problem is that for example size of file is 10MB. I pause when it downloaded 3MB and then resume to download and when download is finish size of file become 13MB. It doesnt start from downloaded size on resume, it start download from begining of bytestream. so file become 13MB. What is wrong with code?

Upvotes: 14

Views: 6733

Answers (4)

blackorbs
blackorbs

Reputation: 625

How I did it with OkHttp + Okio

Save the totalBytes downloaded and add tag to the request so you can cancel it to pause download.

        Request.Builder requestBuilder = new Request.Builder();
        if(isResume) requestBuilder.addHeader("Range","bytes="+downloadedSize+"-");
        okHttpClient.newCall(requestBuilder.url(downloadUrl).tag(filePath).build()).enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {e.printStackTrace();}
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) {
                if(response.isSuccessful()){
                    BufferedSink sink=null; BufferedSource source=null;
                    try {
                        ResponseBody responseBody = response.body();assert responseBody != null;
                        source = responseBody.source(); 
                        File downloadFile = new File(filePath);
                        sink = Okio.buffer(downloadFile.exists() ? Okio.appendingSink(downloadFile) : Okio.sink(downloadFile)); //append if resume
                        int bufferSize = 8 * 1024;
                        for (long bytesRead; (bytesRead = source.read(sink.getBuffer(), bufferSize)) != -1; ) {
                            sink.emit(); downloadedSize+=bytesRead;
                            publishProgress(downloadedSize);// you can show download progress update. You can use Handler to update UI on main thread 
                        }
                        Log.d(TAG, "File successfully downloaded and saved to storage.");
                    }
                    catch (IOException e){e.printStackTrace();}
                    finally {
                        try {if(sink!=null) {sink.flush();sink.close();} if(source != null)source.close();}catch (IOException e){e.printStackTrace();}
                    }
                }
            }
        });

To pause download, cancel the request using the tag;

    private void cancel(String tag){
        for(Call call: okHttpClient.dispatcher().queuedCalls()){
            if(tag.equals(call.request().tag())) call.cancel();
        }
        for(Call call: okHttpClient.dispatcher().runningCalls()){
            if(tag.equals(call.request().tag())) call.cancel();
        }
    }

Upvotes: 0

alperk01
alperk01

Reputation: 343

Based on Alexander answer, I have tried both ways he recommended. The first way produced errors when download stopped and then continued after an app restart. I have found the second way is more stable. Also there is a syntax error in the code. A sample code is here.

        File file = new File(path);
        long availableFileLength = file.exists() && file.isFile() ? file.length() :0;
        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.addHeader("Range", "bytes=" + String.valueOf(availableFileLength) + "-");
        Request request = requestBuilder.url(url).build();
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .build();
        ResponseBody responseBody = okHttpClient.newCall(request).execute().body();

        InputStream input = new BufferedInputStream(responseBody.byteStream());
        OutputStream output = new FileOutputStream(downloadPath, true);

        long currentDownloadedSize = 0;
        long currentTotalByteSize = responseBody.contentLength();
        byte[] data = new byte[1024];
        int count = 0;
        while ((count = input.read(data)) != -1) {
             currentDownloadedSize += count;
             output.write(data, 0, count);   
        }

Upvotes: 0

Jeeva
Jeeva

Reputation: 1057

val call = client.newCall(request)

call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        listener.onFail(e)
    }

    override fun onResponse(call: Call, response: Response) {
        // write the stream to a file (downloading happening)
        val stream = response.body?.byteStream()
    }
})

// this will stop the downloading
call.cancel()

to resume use the "Range" header with your request and append the stream to already downloaded file.

Upvotes: 1

Alexander
Alexander

Reputation: 1770

FIRST WAY

I tried a lot of codes and finally I solved with BufferedSource source = responseBody.source(); source.skip(downloadedSize);

Response request = new Request.Builder().url(url).build();
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();

if(isResume)
    source.skip(downloadedSize);

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
} else {
    output = new FileOutputStream(file, false);    
}

long currentDownloadedSize = 0;
long currentTotalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;
while ((count = input.read(data)) != -1) {
    currentDownloadedSize += count;
    output.write(data, 0, count);   
}

It worked successfully. I think I'm lucky :)

SECOND WAY

I added header for skip downloaded bytes and it worked.

Request.Builder requestBuilder = new Request.Builder();
if (isResume) {
    requestBuilder.addHeader("Range", "bytes=" + String.valueOf(downloadedSize) + "-");
}
Response request = requestBuilder.url(url).build();
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
} else {
    output = new FileOutputStream(file, false);    
}

long currentDownloadedSize = 0;
long currentTotalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;
while ((count = input.read(data)) != -1) {
    currentDownloadedSize += count;
    output.write(data, 0, count);   
}

Upvotes: 24

Related Questions