A solution for Afinal breakpoint download BUG, afinal breakpoint download bug

Source: Internet
Author: User

A solution for Afinal breakpoint download BUG, afinal breakpoint download bug

Welcome to my Android Development Group [257053751]

As the first Android development framework Afinal in China, I believe many developers know it. Although there are some methods with the iterations of the Android version that have better solutions, the author does not maintain them any more, but no one doubts the value of Afinal.

I recently reconstructed a resumable download function of the KJFrameForAndroid framework. I have found a BUG in the FinalHttp. download () method by referring to many examples.

First, we will introduce the implementation principle of download in afinal. Unlike many other download methods, afinal uses a single-thread breakpoint download with no database or additional file operations. So how does one implement resumable data transfer? It mainly uses a FileOutputStream constructor.,View api documentation

The append parameter allows you to continue writing data at the end of a file. This enables resumable data transfer.

After understanding the implementation principle, let's look at the code (slightly changed parameter name). You can see the complete code here.

Public Object handleEntity (HttpEntity entity, EntityCallBack callback, String target, boolean isResume) throws IOException {if (TextUtils. isEmpty (target) | target. trim (). length () = 0) return null; File targetFile = new File (target); if (! TargetFile. exists () targetFile. createNewFile (); if (mStop) {return targetFile;} long current = 0; FileOutputStream OS = null; if (isResume) {current = targetFile. length (); OS = new FileOutputStream (target, true);} else {OS = new FileOutputStream (target) ;}if (mStop) {return targetFile;} InputStream input = entity. getContent (); long count = entity. getContentLength () + current; if (current> = Count | mStop) {return targetFile;} int readLen = 0; byte [] buffer = new byte [1024]; while (! MStop &&! (Current> = count) & (readLen = input. read (buffer, 0, 1024)> 0) {// not all read OS. write (buffer, 0, readLen); current + = readLen; callback. callBack (count, current, false);} callback. callBack (count, current, true); return targetFile ;}

According to the code, we can see an obvious problem-the stream is not closed. This problem is easy to solve. I will not explain it much if I find it closed. Another question is:

Long count = entity. getContentLength () + current;

The size of the remote file is constantly increasing. Each time the download task is paused, the size of the remote file is increased once, And the size is equal to the size of the local part file that has been downloaded. There are two consequences: 1. This breakpoint download function is not used at all, and each download starts from 0. 2. As local files are continuously uploaded, they become larger and larger.

For example, the remote file size is 1024 B, and the local file size is B. The user suspends the download. Next time you download the file again, the program reads the remote file size again, which is 1024 + 512, while the local file is 512, input. read reads data from 0 again, while local files are written from 512, and then downloaded. A total of 512 + 1024 bytes were downloaded during the two downloads. As the local file is resumed, the first download of the 512 fragment file, and the second download of the complete 1024 file, finally, the downloaded file is 1536 bytes in size. (By the way, this file can be opened normally, but the open speed will be slower than the source file)

After finding the problem, is there a solution? Simply count = entity. getContentLength () + current; changed to count = entity. getContentLength (); then the current <count in the while loop is removed? You can try it. First of all, this still cannot achieve the purpose of resumable data transfer.

InputStream input = entity.getContent();

If the obtained Stream does not skip the downloaded part, it cannot achieve the purpose of saving traffic for continuous download. I think this can be understood.

OK, so InputStream has a skip method. you can skip the downloaded part. NO, NO, because some data in the FileOutPutStream that is responsible for file writing and writing is corrupted, cannot be used, and the number of bytes you skip is not the number of bytes you actually need to skip.

Continue, check the code. Here is my solution:

Public File handleEntity (HttpEntity entity, DownloadProgress callback, File save, boolean isResume) throws IOException {long current = 0; RandomAccessFile file = new RandomAccessFile (save, "rw "); if (isResume) {current = file. length ();} InputStream input = entity. getContent (); long count = entity. getContentLength (); if (mStop) {FileUtils. closeIO (file); return save;} current = input. skip (curren T); file. seek (current); int readLen = 0; byte [] buffer = new byte [1024]; while (readLen = input. read (buffer, 0, 1024 ))! =-1) {if (mStop) {break;} else {file. write (buffer, 0, readLen); current + = readLen; callback. onProgress (count, current) ;}} callback. onProgress (count, current); if (mStop & current <count) {// The user proactively stops FileUtils. closeIO (file); throw new IOException ("user stop download thread");} FileUtils. closeIO (file); return save ;}

We can see that a RandomAccessFile class that supports reading and writing random access files is used to replace the resumable FileOutPutStream, and the current value is assigned again.

 current = input.skip(current);

It perfectly solves the file corruption problem caused by the unavailability of fragment files.

The above method is actually a small problem. We all know in Android that the download process is usually accompanied by a progress bar. This problem is that when the download continues after the pause, because the previous section of the file that is unavailable and damaged takes up the size, the progress bar may rebound sharply when the download is resumed. This is very bad for the user experience. After all, who wants me to wait for a long time to read the progress, so many sub-accounts are returned.

The solution is to provide psychological comfort, and read the following code:

Public File handleEntity (HttpEntity entity, DownloadProgress callback, File save, boolean isResume) throws IOException {long current = 0; RandomAccessFile file = new RandomAccessFile (save, "rw "); if (isResume) {current = file. length ();} InputStream input = entity. getContent (); long count = entity. getContentLength () + current; if (mStop) {FileUtils. closeIO (file); return save;} // In fact, this write is incorrect. So this is for the user experience. No one wants to download the progress bar by themselves, this is because a pause is missing a large string/*** the actual statement here should be: <br> * current = input. skip (current); <br> * file. seek (current); <br> * According to the explanation in the JDK document: Inputstream. the skip (long I) method skips I bytes and returns the number of actually skipped bytes. <Br> * There are many reasons for this. It is only possible that n Bytes have reached the end of the file before skipping. Here I guess it may be caused by damage to the fragment file. */File. seek (input. skip (current); int readLen = 0; byte [] buffer = new byte [1024]; while (readLen = input. read (buffer, 0, 1024 ))! =-1) {if (mStop) {break;} else {file. write (buffer, 0, readLen); current + = readLen; callback. onProgress (count, current) ;}} callback. onProgress (count, current); if (mStop & current <count) {// The user proactively stops FileUtils. closeIO (file); throw new IOException ("user stop download thread");} FileUtils. closeIO (file); return save ;}

I personally do not advocate multithreading for downloading android. The main reason is that mobile phones generally do not download large files, and the thread overhead of multithreading, coupled with the IO overhead produced by using databases or additional record files, is not small, multithreading is not significant.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.