1. DownloadTask 的创建
DownloadTask 的创建使用 Builder模式
进行创建的。从其源码中可以找到 Builder
类的定义。
1.1. 我们先看一看 DownloadTask 的构造函数
java
代码解读
复制代码
// 参数非常多,很多参数都是可以使用默认值,有几个参数是必须的 public DownloadTask( String url, Uri uri, int priority, int readBufferSize, int flushBufferSize, int syncBufferSize, int syncBufferIntervalMills, boolean autoCallbackToUIThread, int minIntervalMillisCallbackProcess, Map<String, List<String>> headerMapFields, @Nullable String filename, boolean passIfAlreadyCompleted, boolean wifiRequired, Boolean filenameFromResponse, @Nullable Integer connectionCount, @Nullable Boolean isPreAllocateLength )
1.2. 再来看看 DownloadTask.Builder 类
Builder
类提供了三个构造函数分别如下:
java
代码解读
复制代码
/** * Create the task builder through {@code url} and the file's parent path and the filename. * * @param url the url for the task. * @param parentPath the parent path of the file for store download data. * @param filename the filename of the file for store download data. */ public Builder(@NonNull String url, @NonNull String parentPath, @Nullable String filename) { this(url, Uri.fromFile(new File(parentPath))); if (Util.isEmpty(filename)) { this.isFilenameFromResponse = true; } else { this.filename = filename; } } /** * Create the task builder through {@code url} and the store path {@code file}. * * @param url the url for the task. * @param file the file is used for store download data of the task. */ public Builder(@NonNull String url, @NonNull File file) { this.url = url; this.uri = Uri.fromFile(file); } /** * Create the task builder through {@code url} and the store path {@code uri}. * * @param url the url for the task. * @param uri the uri indicate the file path of the task. */ public Builder(@NonNull String url, @NonNull Uri uri) { this.url = url; this.uri = uri; if (Util.isUriContentScheme(uri)) { this.filename = Util.getFilenameFromContentUri(uri); } }
从 Builder
的构造函数中可以看到,DownloadTask
的创建需要两个必须的参数 url
和 uri
。 其中,url
是需要下载的资源地址,uri
是资源下载的存储路径。
而 filename
是存储的文件名,如果有特殊要求,可以特别指定,没有特殊要求的话,就忽略,有内部自动计算。
至于其他的参数都可以使用默认值,无须额外设置,我们来看一下其他参数的默认值配置。
java
代码解读
复制代码
/** * Build the task through the builder. * * @return a new task is built from this builder. */ public DownloadTask build() { return new DownloadTask(url, uri, priority, readBufferSize, flushBufferSize, syncBufferSize, syncBufferIntervalMillis, autoCallbackToUIThread, minIntervalMillisCallbackProcess, headerMapFields, filename, passIfAlreadyCompleted, isWifiRequired, isFilenameFromResponse, connectionCount, isPreAllocateLength); } // 给资源地址配置特殊的请求头信息,默认不配置 private volatile Map<String, List<String>> headerMapFields; // 下载任务优先级参数,由于是 int 类型,默认值为 0 // More larger more high. private int priority; public static final int DEFAULT_READ_BUFFER_SIZE = 4096/* byte **/; // 下载过程中从返回的响应数据中,从输入流中一次读取数据的buffer大小,默认为 4096 private int readBufferSize = DEFAULT_READ_BUFFER_SIZE; public static final int DEFAULT_FLUSH_BUFFER_SIZE = 16384/* byte **/; // 下载过程中对从输入流中读取的数据,写入本地文件时写入数据的buffer大小,默认为 16384 (4096*4) private int flushBufferSize = DEFAULT_FLUSH_BUFFER_SIZE; /** * Make sure sync to physical filesystem. */ public static final int DEFAULT_SYNC_BUFFER_SIZE = 65536/* byte **/; // 同步写入到物理文件时的缓存大小,默认为 65536 private int syncBufferSize = DEFAULT_SYNC_BUFFER_SIZE; public static final int DEFAULT_SYNC_BUFFER_INTERVAL_MILLIS = 2000/* millis **/; // 同步写入到物理文件时使用的间隔时间,默认为 2000 private int syncBufferIntervalMillis = DEFAULT_SYNC_BUFFER_INTERVAL_MILLIS; public static final boolean DEFAULT_AUTO_CALLBACK_TO_UI_THREAD = true; // 回调是否发送到UI线程,默认为是 private boolean autoCallbackToUIThread = DEFAULT_AUTO_CALLBACK_TO_UI_THREAD; public static final int DEFAULT_MIN_INTERVAL_MILLIS_CALLBACK_PROCESS = 3000/* millis **/; // 最小的进度回调间隔时间 private int minIntervalMillisCallbackProcess = DEFAULT_MIN_INTERVAL_MILLIS_CALLBACK_PROCESS; // 特殊指定的下载文件保存名称 private String filename; public static final boolean DEFAULT_PASS_IF_ALREADY_COMPLETED = true; /** * if this task has already completed judged by * {@link StatusUtil.Status#isCompleted(DownloadTask)}, callback completed directly instead * of start download. */ // 是否对已经下载完成的重复任务直接跳过,不跳过的话会执行重新覆盖下载 private boolean passIfAlreadyCompleted = DEFAULT_PASS_IF_ALREADY_COMPLETED; public static final boolean DEFAULT_IS_WIFI_REQUIRED = false; // 是否需要在WiFi网络下才能下载 private boolean isWifiRequired = DEFAULT_IS_WIFI_REQUIRED; // 文件名是否从响应数据的头部信息中获取 private Boolean isFilenameFromResponse; // 设置该任务的 connection establish 数量,如果该任务已经对过去进行了 split block 并等待恢复,则这个设置的连接数不会真正产生影响。 private Integer connectionCount; // 设置从 trial-connection 获取 resource-length 后是否需要预先分配文件的长度。 private Boolean isPreAllocateLength;
2. 开始任务下载 DownloadTask.enqueue/execute
任务创建完成后,就可以启动任务下载了
java
代码解读
复制代码
DownloadTask task = new DownloadTask.Builder(url, file).build(); // 异步执行,放到下载任务的队列里面进行排队 task.enqueue(listener) // or // 立即执行,直接插入到排队任务的前面开始执行 task.execute(listener)
下面我们分别对这两个过程先进行简单的分析:
2.1. DownloadTask.enqueue 流程分析
DownloadTask.enqueue
调用 DownloadDispatcher.enqueue
方法。
java
代码解读
复制代码
/** * Enqueue the task with the {@code listener} to the downloader dispatcher, what means it will * be run when resource is available and on the dispatcher thread pool. * <p> * If there are more than one task need to enqueue please using * {@link #enqueue(DownloadTask[], DownloadListener)} instead, because the performance is * optimized to handle bunch of tasks enqueue. * * @param listener the listener is used for listen the whole lifecycle of the task. */ public void enqueue(DownloadListener listener) { this.listener = listener; OkDownload.with().downloadDispatcher().enqueue(this); }
跟踪 DownloadDispatcher.enqueue
方法,进行分析
enqueue
方法:将任务放入到下载任务队列中进行排队,同时会根据任务优先级 priority
参数,进行对任务的排序处理。
java
代码解读
复制代码
/** * 将下载任务添加到队列中 * 此方法用于在执行某些操作前后增加和减少跳过调用计数,以控制并发访问 * * @param task 要添加到队列的下载任务 */ public void enqueue(DownloadTask task) { // 在添加任务前后增加和减少跳过调用计数,以控制并发访问 skipProceedCallCount.incrementAndGet(); enqueueLocked(task); skipProceedCallCount.decrementAndGet(); } /** * 同步方法,用于将下载任务添加到队列中 * 该方法在执行前会检查任务是否已经完成和是否存在冲突 * 如果队列中已存在相同任务或任务之间存在冲突,则不会添加 * * @param task 要添加到队列的下载任务 */ private synchronized void enqueueLocked(DownloadTask task) { // 打印调试信息,表示正在将单个任务添加到队列中 Util.d(TAG, "enqueueLocked for single task: " + task); // 如果任务已经完成,则直接返回,不再添加到队列中 if (inspectCompleted(task)) return; // 如果任务与队列中其他任务存在冲突,则直接返回,不添加到队列中 if (inspectForConflict(task)) return; // 记录当前准备异步调用的集合大小,用于后续判断是否有变化 final int originReadyAsyncCallSize = readyAsyncCalls.size(); // 将任务添加到队列中,忽略优先级 enqueueIgnorePriority(task); // 如果准备异步调用的集合大小发生变化,则对集合进行排序 if (originReadyAsyncCallSize != readyAsyncCalls.size()) { Collections.sort(readyAsyncCalls); } } /** * 将下载任务加入合适的队列,忽略其优先级 * 此方法主要用于在下载队列已满时,将新来的下载任务加入队列,而不考虑其优先级 * 它通过创建一个下载调用对象来代表下载任务,并根据当前运行中的异步下载数量决定是否立即执行该任务 * 如果运行中的异步下载数量小于最大并行运行数量,则新任务会被立即执行; * 否则,新任务将被加入待处理队列中等待执行 * * @param task 要加入队列的下载任务 */ private synchronized void enqueueIgnorePriority(DownloadTask task) { // 创建下载调用对象,设置为异步执行 final DownloadCall call = DownloadCall.create(task, true, store); // 如果当前运行中的异步下载数量小于最大并行运行数量,则将新任务加入运行队列并执行 if (runningAsyncSize() < maxParallelRunningCount) { runningAsyncCalls.add(call); getExecutorService().execute(call); } else { // 如果当前运行中的异步下载数量等于最大并行运行数量,则将新任务加入待处理队列 readyAsyncCalls.add(call); } }
2.2. DownloadTask.execute 流程分析
DownloadTask.execute
调用 DownloadDispatcher.execute
方法。
java
代码解读
复制代码
/** * Execute the task with the {@code listener} on the invoke thread. * * @param listener the listener is used for listen the whole lifecycle of the task. */ public void execute(DownloadListener listener) { this.listener = listener; OkDownload.with().downloadDispatcher().execute(this); }
跟踪 DownloadDispatcher.execute
方法,进行分析
execute
方法:将任务直接加入到正在下载任务队列中,然后立即启动下载流程。
java
代码解读
复制代码
/** * 执行下载任务 * * 此方法首先检查任务是否已完成或是否存在冲突如果任务未完成且无冲突,则创建一个DownloadCall实例并将其添加到运行中的同步调用列表中 * 最后,调用syncRunCall方法来执行实际的下载操作 * * @param task 要执行的下载任务 */ public void execute(DownloadTask task) { // 打印执行任务的日志信息 Util.d(TAG, "execute: " + task); final DownloadCall call; synchronized (this) { // 检查任务是否已完成,如果已完成则直接返回 if (inspectCompleted(task)) return; // 检查任务是否与正在运行的任务有冲突,如果有冲突则直接返回 if (inspectForConflict(task)) return; // 创建DownloadCall实例,准备执行下载任务 call = DownloadCall.create(task, false, store); // 将新创建的DownloadCall实例添加到运行中的同步调用列表中 runningSyncCalls.add(call); } // 执行DownloadCall实例,开始下载任务 syncRunCall(call); }
分析到这里,我们可以看到 DownloadTask.enqueue/execute
都是调用了 DownloadDispatcher.enqueue/execute
方法,而在其内部的处理中,添加/启动任