14

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?

Alexander
  • 1,720
  • 4
  • 22
  • 40
  • 1
    You're creating a new request and sending it. That causes the response to hold the entire file. I'm not sure if okHttp has the ability to download partial responses, but if it does that's not how you do it. You'd at the least have to pass in some data about where to start the download from. – Gabe Sechan Apr 11 '17 at 17:38
  • I don't see any problem with your approach may be your download size was 0 which in turns skip nothing. I really don't find any difference between the question and the answer described in a first way. If there is any specific difference then do let me know – Shubham AgaRwal May 17 '19 at 07:31
  • @Killer differences is source.skip(downloadedSize) . The code that you dont see I get file download status and downloaded size from db. For example file size is 5.5mb and I downloaded 3mb in past download process. The next downloading process must call source.skip(3mb to bytes) – Alexander May 17 '19 at 08:17

3 Answers3

24

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);   
}
Alexander
  • 1,720
  • 4
  • 22
  • 40
  • How to pause the download in the first place? Can you share me some ideas on how to handle the resume part on the button click? – dazed'n'confused Nov 04 '19 at 06:13
  • @dazed'n'confused sorry but I could not understand your question. What is your problem with resume/pause ? – Alexander Nov 04 '19 at 14:24
  • I am trying to download files using a service and I need to pause the download in the service how can i do that? – dazed'n'confused Nov 04 '19 at 16:05
  • @dazed'n'confused you need to save last downloaded size of file when it paused. On resume you will use downloaded size for starting download from which bytes. Actually your question's answer is in my answer https://stackoverflow.com/a/43356615/1135367 – Alexander Nov 07 '19 at 14:08
  • 1
    second way is better, in first way when you use skip, client must download file then skip N byte from first. for example you start download 100 MB file and resume it at 50 MB, when you start again client download from first but skip 50 MB and after 50 MB you can read output. – Golil May 17 '21 at 04:23
1
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.

Jeeva
  • 1,029
  • 3
  • 15
  • 21
0

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);   
        }
alperk01
  • 183
  • 1
  • 7