Sorry it took so long. I’ve been so busy lately.

In the previous Retrofit2 repackaging exercise — Multithreaded Download and Resumable (1), we introduced the structure diagram of the project. This time, we started with the program entry DownLoadManager and the actual download class DownLoadTask. I know you want the code

DownLoadManager

Before starting the DownLoadManager, we need to clarify the data structure of the download callback and download task.

Download the task data structure

What data structure do we use to express our download tasks? Here I’ve chosen to use a List collection to store all the download tasks. Each task is a DownLoadEntity, the URL is the download address, and saveName is the save address. For now, those are the only two properties you care about.

public class DownLoadEntity {
    public int dataId; 
    public String url;
    public long end;
    public long start;
    public long downed;
    public long total;
    public String saveName;
    public List multiList;
}
Copy the code

Download the callback

What download status do you care about during the download process? 1. Start download: After the user triggers the download condition, the percentage is called back after completing a series of tasks (judge whether the downloaded data is complete, obtain the total length of all tasks, calculate the downloaded percentage, and create the download task). Simply put, pull back when everything is ready. 2. Cancel download: Callback is performed once the cancellation condition is triggered. 3. Download: Trigger condition is not every I/O, will be called back, in order to save resources, here every download 1MB callback percentage (this of course you can set). 4. Complete download: Callback after all tasks of a download request are complete. 5. Download error: Abnormal status callback occurs during the download process.

public interface DownLoadBackListener {
    void onStart(double percent);
    void onCancel();
    void onDownLoading(double percent);
    void onCompleted();
    void onError(DownLoadEntity downLoadEntity,Throwable throwable);
}

Copy the code


The onError method is an exception. A download request can have dozens of urls. What do you do if a task fails? I think you would pull out the failed URL and request a separate download, and then limit the number of repeats, say 10, and if it still fails after 10, you might prompt the user that the download failed. In this wrapper, you don’t have to worry about that anymore, because you’ve handled the failure case for you, and every failed URL is redownloaded, ten attempts, and if all failed, the onError callback is done. The last failed download entity and the cause of the failure have been called back to you, as to how to deal with it, you decide.

Three, download entrance

DownLoadManager is the main entry point for downloading. Combining the above mentioned download structure and callback, we provide download methods:

public void downLoad(final List list, final String tag, final DownLoadBackListener downLoadTaskListener, final long multiLine) { mExecutorService.submit(new Runnable() { @Override public void run() { DownLoadRequest downLoadRequest = new DownLoadRequest(mDownLoadDatabase,downLoadTaskLister, list, multiLine); downLoadRequest.start(); mDownLoadRequestMap.put(tag, downLoadRequest); }}); } The copyright belongs to the author, please contact the author to obtain authorization for reprinting, and mark "Jane book author".Copy the code


List: Download data for the entire request.


Tag: Since we cache the download data for each request, we use tags to distinguish between requests. If you don’t already know this, please browse my other article

Repackaging Retrofit2: Synchronous versus Asynchronous Requests”, the same meaning as the Tag in the article.


DownLoadBackListener: The download callback described above.


MultiLine. The program uses multithreading by default. The default value is 10 * 1024 * 1024 bytes (10MB). For example, if a URL is 50MB in size, the application will automatically split the 50MB into five 10MB downloads. If you don’t want to use multithreading, just pass 0;


Of course, if you want to simply use the default values, the program also provides the corresponding polymorphic methods:

Public void downLoad(final List List, final String tag, final DownLoadBackListener downLoadTaskListener) { downLoad(list, tag, downLoadTaskListener, MULTI_LINE); }Copy the code

MDownLoadRequestMap = new ConcurrentHashMap<>(); Key is a tag and value is a DownLoadRequest. It also provides the cancel() method to cancel the task. The implementation is the same as in Retrofit2 Reencapsulation: Synchronous vs. Asynchronous Requests, but I won’t go into details here. As you may have noticed, the mExecutorService thread pool is used in the downLoad() method. The reason for opening a new thread is to handle main thread blocking before the onStart callback. We need to get the total length of all urls for the current task (how else can I call back the percentage?). , the general idea is as follows: firstly, all urls will be iterated. For each URL, the local database will be queried first to see whether there is a task record of the current URL. If so, the data will be extracted. If not, make an asynchronous network request to get the download length. We have a round-robin mechanism that waits for all urls to reach their length before downloading. So this part up here, it must be in sync! It must be synchronized! It must be synchronized! (Of course, all web requests for urls are done asynchronously.) That is, I wait until all urls are finished before I can actually start the download task. If your download request has nearly 100 urls, this part will take about 2~3 seconds. This short 2~3 seconds is fatal to the UI thread. The problem is that all callbacks to downLoadTaskListener are now in an asynchronous thread. How to call back to update the UI in an asynchronous thread does not need to be handled by the user. You can use a Handler that gets the main thread Looper, which is familiar if you’ve seen the source code for Retrofit, which is available in DownLoadRequest.

With that said, here’s a simple demo to use to cover all the features of the DownLoadManager entry class.




demo

DownLoadTask

As mentioned above, the real DownLoadTask is performed in the DownLoadTask. We have already created the DownLoadService. We only need to call the Api of the DownLoadService in the Task to perform I/O operations. URL and Task can be one-to-one or one-to-many. Here we use the Builder pattern to create Task instances:

public static final class Builder { private DownLoadEntity mDownModel; private DownLoadTaskListener mDownLoadTaskListener; public Builder downLoadModel(DownLoadEntity downLoadEntity) { mDownModel = downLoadEntity; return this; } public Builder downLoadTaskListener(DownLoadTaskListener downLoadTaskListener) { mDownLoadTaskListener = downLoadTaskListener; return this; } public DownLoadTask build() { if (mDownModel.url.isEmpty()) { throw new IllegalStateException("DownLoad URL required."); } if (mDownLoadTaskListener == null) { throw new IllegalStateException("DownLoadTaskListener required."); } if (mDownModel.end == 0) { throw new IllegalStateException("End required."); } return new DownLoadTask(mTaskId, mDownModel, mDownLoadTaskListener); }}Copy the code


(Code typesetting for a long time, do not know why there is still such a big gap…)


DownLoadTaskListener: This is a per-task callback, which is different from the DownLoadBackListener we talked about above, which is a callback that handles the UI, more like a total callback in a sense, and DownLoadTaskListener is more about the details, Is the callback for each download task, so he is more concerned with the download task itself:

public interface DownLoadTaskListener {
    void onStart();
    void onCancel(DownLoadEntity downLoadEntity);
    void onDownLoading(long downSize);
    void onCompleted(DownLoadEntity downLoadEntity);
    void onError(DownLoadEntity downLoadEntity, Throwable throwable);
}Copy the code

The callback methods are basically the same as the DownLoadBackListener, except that the parameters of each method are different. The onCancel onCompleted method returns a DownLoadEntity. These callbacks are processed in DownLoadRequest. The final callback is to DownLoadTaskListener. DownLoadEntity: Each DownLoadEntity is cached in the DB, combined with the object properties given above, url and saveName, which are described above and will not be explained here. DataID: database primary key with unique ID for each entity. Property to cache the local Map’s DownLoadTask(explained in DownLoadRequest). For example, if we have a URL of 50MB and a multithreaded download is broken down into five downloadentities, These five entities are stored in the multiList and the total value is 50MB instead of 10MB. These two properties have nothing to do with downloads. The details are explained in DownLoadRequest.

DownLoadTask implements the Runnable interface, and we’ll focus on the run method:




run()

Line 49 sets thread priority to highest. Lines 50-55 Call the DownLoadService interface method we defined in the previous article to generate a Retrofit Call and determine whether downed is 0. If so, start directly from downed+start. The following code is easy to understand: take the Response of the Call and fetch the body of the Response. Lines 63 perform I/O operations, and 66-75 handle failures and release resources. Take a look at the key writeToFile method:




writeToFile-part1

Here the logic is very simple, first determine whether the file exists, and then create a file, 88 mark start to write file location, 93-95 set file read buffer, I believe familiar with the I/O operation, here will not be unfamiliar, unfamiliar friends please query information by yourself, here do not explain. Read on:




writeToFile-part2

We’ve optimized this so that it would be too extravagant to call back every time we write 4096 bytes, so we set a constant private final Long CALL_BACK_LENGTH = 1024 * 1024; Each 1MB callback is performed. To count the number of downloads before each callback, we define the private long mFileSizeDownloaded; Line 105 Write the mFileSizeDownloaded+read; If the current mFileSizeDownloaded is greater than CALL_BACK_LENGTH, that is, the onDowLoading method is called and the mFileSizeDownloaded is set to 0, The mNeedDownSize attribute counts the remaining bytes of the download, 112-115 lines. If the remaining bytes are below the callback threshold, the callback will be performed after the last byte is downloaded.




writeToFile-part3

Lines 122-130 close resources 132 to the end is where you need to handle IO exceptions, where you need to base your business on exception handling, which is where you need to customize. When a thread is canceled, InterruptedIOException is raised (don’t ask me why, thread basics). When the network is disconnected, a SocketTimeoutException is raised. Our business logic here is that unless the user cancels the SocketTimeoutException, it is considered an Error. The different state callback codes are shown below;




Multistate callback

Tips

There is still the end of the last article, this article has been written for nearly two weeks, I am not very satisfied with the quality. Let’s post the code first. This is to finally give out, we do not understand the code wank it… I hope my favorite friends can help me. If there are bugs in the use, please give me feedback. Wechat: hLY1501 Email :[email protected]