主目录见:Android高级进阶知识(这是总目录索引)
下载器Github地址:FileDownloader
文件下载在Android的开发中应该可以说是都会用到,所以一个完善的好的下载工具应该是不可或缺的,第一次看这个框架还是感觉类比较多的,应该是作者为了类的职责划分更加明确吧,但是个人感觉适当为好。正如介绍所说,这个框架是多任务,多线程,支持断点恢复,高并发,简单易用且支持多进程的。确实是个不错的框架,值得我们来读读代码,不过我也只是看过一下午,如果有理解不到位的大家谅解一下。
一.目标
这篇文章应该来说也是比较实用的,而且本库的wiki应该也有部分的介绍,但是我们今天的主要目标如下:
1.学习该库中优秀的思维;
2.懂得自己开发或者修改和改进该库。
二.源码解析
首先当你拿到一个库不知道从哪开始的时候,往往就是从使用上入手,所以我们先来看看怎么使用该库,因为使用方式有几种,我们挑其中一种来说明:
1.添加gradle依赖
dependencies {
compile 'com.liulishuo.filedownloader:library:1.6.8'
}
2.在Application初始化
FileDownloader.setupOnApplicationOnCreate(this)
.connectionCreator(new FileDownloadUrlConnection
.Creator(new FileDownloadUrlConnection.Configuration()
.connectTimeout(15_000) // set connection timeout.
.readTimeout(15_000) // set read timeout.
.proxy(Proxy.NO_PROXY) // set proxy
))
.commit();
3.开始下载(这里我挑一个使用任务队列来下载的方式)
final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener);
final List<BaseDownloadTask> tasks = new ArrayList<>();
for (int i = 0; i < count; i++) {
tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1));
}
queueSet.disableCallbackProgressTimes(); // do not want each task's download progress's callback,
// we just consider which task will completed.
// auto retry 1 time if download fail
queueSet.setAutoRetryTimes(1);
if (serialRbtn.isChecked()) {
// start download in serial order
queueSet.downloadSequentially(tasks);
} else {
// start parallel download
queueSet.downloadTogether(tasks);
}
queueSet.start();
那么我们就从初始化入手,我们先来看看FileDownloader#setupOnApplicationOnCreate()
干了什么工作。
1.setupOnApplicationOnCreate
public static DownloadMgrInitialParams.InitCustomMaker setupOnApplicationOnCreate(Application application) {
final Context context = application.getApplicationContext();
//将context缓存在FileDownloadHelper中
FileDownloadHelper.holdContext(context);
//实例化InitCustomMaker
DownloadMgrInitialParams.InitCustomMaker customMaker = new DownloadMgrInitialParams.InitCustomMaker();
//赋值给DownloadMgrInitialParams然后保存在CustomComponentHolder中
CustomComponentHolder.getImpl().setInitCustomMaker(customMaker);
return customMaker;
}
我们看到这个方法没有做什么工作,这也是合理的,因为在Application#onCreate
避免做过多的工作导致启动速度变慢。这个方法会返回一个InitCustomMaker
类的实例,所以程序会调用这个类connectionCreator()
方法:
public InitCustomMaker connectionCreator(FileDownloadHelper.ConnectionCreator creator) {
this.mConnectionCreator = creator;
return this;
}
这个方法就是将一个连接创建器赋值给InitCustomMaker
类中的变量,返回this是为了链式操作需要,因为InitCustomMaker
这里还有很多可以自定义的方法。这样我们的初始化工作就完成了,我们来看我们的开始下载部分。
2.FileDownloadQueueSet的设置
这个类是用来配置下载任务和启动下载任务的,首先我们看到构造函数里面给他设置了一个FileDownloadListener
实例用于回调。然后会在串行下载或者并行下载的时候会给他传一个任务集合,我们首先来看看任务集合的创建和添加。任务的创建是在FileDownloader#create
中创建的:
public BaseDownloadTask create(final String url) {
return new DownloadTask(url);
}
通过这个我们可以知道,一个下载任务对应一个DownloadTask
,我们看下它的构造函数:
DownloadTask(final String url) {
this.mUrl = url;
mPauseLock = new Object();
final DownloadTaskHunter hunter = new DownloadTaskHunter(this, mPauseLock);
mHunter = hunter;
mMessageHandler = hunter;
}
这里除了给DownloadTask
的mUrl赋值之外,还实例化了一个DownloadTaskHunter
实例,我们看到这里参数将DownloadTask
实例传给了它的构造函数,然后传了mPauseLock
用来做线程锁操作。我们看下DownloadTaskHunter
构造函数:
DownloadTaskHunter(ICaptureTask task, Object pauseLock) {
mPauseLock = pauseLock;
mTask = task;
final DownloadSpeedMonitor monitor = new DownloadSpeedMonitor();
mSpeedMonitor = monitor;
mSpeedLookup = monitor;
mMessenger = new FileDownloadMessenger(task.getRunningTask(), this);
}
可以看到除了传进来的两个参数赋值之外,还是实例化了DownloadSpeedMonitor
类,这个类主要是用来监测下载进度的。同时还实例化了FileDownloadMessenger
类,这个类是用来给回调接口FileDownloadListener
发送消息的。这样我们大致看了下载任务DownloadTask
的创建过程。
接下来就是FileDownloadQueueSet
类中一些方法的设置,例如我们这里使用到的disableCallbackProgressTimes()
方法,这个方法是设置了的话,那么我们就监听不到一个任务的下载进度了,只有任务下载完成的回调。与这个方法对应的还有setCallbackProgressTimes()
设置更新进度回调次数,setCallbackProgressMinInterval()
设置了更新进度的最小间隔时间。setAutoRetryTimes()
方法是设置下载失败重试的次数。然后会把上面创建的任务集合赋值给FileDownloadQueueSet
类,这里调用的方法是downloadSequentially()
和downloadTogether()
,这两个方法唯一的不同就是会设置标志位isSerial,来说明是串行下载还是并行下载。最后最重要的当然就是start()
方法了,这里就真正启动开始下载了。
3.FileDownloadQueueSet start
public void start() {
//遍历任务集合
for (BaseDownloadTask task : tasks) {
//设置任务的回调监听为传进来的FileDownloadListener实例
task.setListener(target);
if (autoRetryTimes != null) {
//设置失败重试次数
task.setAutoRetryTimes(autoRetryTimes);
}
if (syncCallback != null) {
//设置是否进行同步回调
task.setSyncCallback(syncCallback);
}
if (isForceReDownload != null) {
//是否强制重新下载
task.setForceReDownload(isForceReDownload);
}
if (callbackProgressTimes != null) {
//设置进度回调次数
task.setCallbackProgressTimes(callbackProgressTimes);
}
if (callbackProgressMinIntervalMillis != null) {
//设置进度回调最小间隔时间
task.setCallbackProgressMinInterval(callbackProgressMinIntervalMillis);
}
if (tag != null) {
task.setTag(tag);
}
if (taskFinishListenerList != null) {
//任务结束监听器,只有在非UI现场中调用
for (BaseDownloadTask.FinishListener finishListener : taskFinishListenerList) {
task.addFinishListener(finishListener);
}
}
if (this.directory != null) {
//设置下载下来的文件存放的路径
task.setPath(this.directory, true);
}
if (this.isWifiRequired != null) {
//是否需要只在wifi情况下才下载
task.setWifiRequired(this.isWifiRequired);
}
//这个方法主要是标识下是在队列中的任务,而且会将任务添加到FileDownloadList的任务集合中
task.asInQueueTask().enqueue();
}
//启动下载任务
FileDownloader.getImpl().start(target, isSerial);
}
我们看到这个方法主要是给任务DownloadTask
设置一些参数值,然后将任务添加到FileDownloadList
中的集合里。然后调用FileDownloader#start()
开始下载任务:
public boolean start(final FileDownloadListener listener, final boolean isSerial) {
if (listener == null) {
FileDownloadLog.w(this, "Tasks with the listener can't start, because the listener " +
"provided is null: [null, %B]", isSerial);
return false;
}
return isSerial ?
getQueuesHandler().startQueueSerial(listener) :
getQueuesHandler().startQueueParallel(listener);
}
这个方法很简单,这里isSerial
标志我们在前面已经设置了,我们这里就走并行下载这条道吧,所以isSerial
为false,所以这里会调用getQueuesHandler().startQueueParallel()
方法来启动并行下载任务,我们这里跟踪到QueuesHandler#startQueueParallel()
方法:
@Override
public boolean startQueueParallel(FileDownloadListener listener) {
final int attachKey = listener.hashCode();
//这里主要防止在添加了下载任务之后又有新的任务到达,所以这里会重新组装任务队列
final List<BaseDownloadTask.IRunningTask> list = FileDownloadList.getImpl().
assembleTasksToStart(attachKey, listener);
//这里会验证是否任务队列里面有任务,然后会调用全局监听器的onRequestStart(),如果有打点/统计等需求可以考虑设置这个下载监听器
if (onAssembledTasksToStart(attachKey, list, listener, false)) {
return false;
}
//遍历任务集合,然后分别启动
for (BaseDownloadTask.IRunningTask task : list) {
task.startTaskByQueue();
}
return true;
}
我们看到前面两步就是重新组装一下任务队列,然后我们会调用任务task中的startTaskByQueue()
方法进行启动任务:
@Override
public void startTaskByQueue() {
startTaskUnchecked();
}
private int startTaskUnchecked() {
if (isUsing()) {
if (isRunning()) {
throw new IllegalStateException(
FileDownloadUtils.formatString("This task is running %d, if you" +
" want to start the same task, please create a new one by" +
" FileDownloader.create", getId()));
} else {
throw new IllegalStateException("This task is dirty to restart, If you want to " +
"reuse this task, please invoke #reuse method manually and retry to " +
"restart again." + mHunter.toString());
}
}
if (!isAttached()) {
setAttachKeyDefault();
}
mHunter.intoLaunchPool();
return getId();
}
前面是判断这个任务是否在执行或者占用,最后会调用DownloadTaskHunter#intoLaunchPool()
方法:
@Override
public void intoLaunchPool() {
synchronized (mPauseLock) {
//如果任务是在空闲状态则现在设置状态为toLaunchPool,否则返回
if (mStatus != FileDownloadStatus.INVALID_STATUS) {
FileDownloadLog.w(this, "High concurrent cause, this task %d will not input " +
"to launch pool, because of the status isn't idle : %d",
getId(), mStatus);
return;
}
mStatus = FileDownloadStatus.toLaunchPool;
}
//获取下载任务
final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();
final BaseDownloadTask origin = runningTask.getOrigin();
//如果设置了全局监听就调用他的onRequestStart
if (FileDownloadMonitor.isValid()) {
FileDownloadMonitor.getMonitor().onRequestStart(origin);
}
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.v(this, "call start " +
"Url[%s], Path[%s] Listener[%s], Tag[%s]",
origin.getUrl(), origin.getPath(), origin.getListener(), origin.getTag());
}
//设置任务准备就绪
boolean ready = true;
try {
//前面给任务设置了path,也就是下载文件存放路径,这里就是创建这个路径
prepare();
} catch (Throwable e) {
ready = false;
FileDownloadList.getImpl().add(runningTask);
FileDownloadList.getImpl().remove(runningTask, prepareErrorMessage(e));
}
if (ready) {
//调用FileDownloadTaskLauncher的launch启动任务
FileDownloadTaskLauncher.getImpl().launch(this);
}
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.v(this, "the task[%d] has been into the launch pool.", getId());
}
}
前面的步骤已经注释的很清楚了,最后我们看到会调用FileDownloadTaskLauncher#launch()
方法来启动任务,传的参数就是本类DownloadTaskHunter
实例,我们看下launch()
方法:
synchronized void launch(final ITaskHunter.IStarter taskStarter) {
mLaunchTaskPool.asyncExecute(taskStarter);
}
这里的mLaunchTaskPool
是一个LaunchTaskPool
类实例,所以会调用LaunchTaskPool#asyncExecute()
方法:
public void asyncExecute(final ITaskHunter.IStarter taskStarter) {
mPool.execute(new LaunchTaskRunnable(taskStarter));
}
mPool是一个自定义的线程池ThreadPoolExecutor
实例,所以调用execute会执行LaunchTaskRunnable#run()
方法:
LaunchTaskRunnable(final ITaskHunter.IStarter taskStarter) {
this.mTaskStarter = taskStarter;
this.mExpired = false;
}
@Override
public void run() {
if (mExpired) {
return;
}
mTaskStarter.start();
}
我们看到run方法中会调用mTaskStarter
的start()
方法,从上面我们可以知道mTaskStarter
是DownloadTaskHunter
实例,所以这里会调用DownloadTaskHunter
的start()
方法,这个方法现在是在子线程中执行的:
@Override
public void start() {
//在前面我们已经设置过标识为toLaunchPool
if (mStatus != FileDownloadStatus.toLaunchPool) {
FileDownloadLog.w(this, "High concurrent cause, this task %d will not start," +
" because the of status isn't toLaunchPool: %d",
getId(), mStatus);
return;
}
//获取下载任务
final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();
final BaseDownloadTask origin = runningTask.getOrigin();
//这个类是用来检查服务是否连接的,因为默认是跨进程的,所以这里会有跨进程的调用检查是否
//连接,如果没有连接的话那么会bindService连接
final ILostServiceConnectedHandler lostConnectedHandler = FileDownloader.getImpl().
getLostConnectedHandler();
try {
//检查服务有没有连接,没有连接就连接,如果是跨进程就启动SeparateProcessService
//服务,不是跨进程就启动SharedMainProcessService服务
if (lostConnectedHandler.dispatchTaskStart(runningTask)) {
return;
}
synchronized (mPauseLock) {
if (mStatus != FileDownloadStatus.toLaunchPool) {
FileDownloadLog.w(this, "High concurrent cause, this task %d will not start," +
" the status can't assign to toFileDownloadService, because the status" +
" isn't toLaunchPool: %d",
getId(), mStatus);
return;
}
//将任务状态设置为toFileDownloadService
mStatus = FileDownloadStatus.toFileDownloadService;
}
//这里会通知FileDownloadMonitor的onTaskBegin说任务要开始下载了,同时会重新
//组装一下任务列表
FileDownloadList.getImpl().add(runningTask);
if (FileDownloadHelper.inspectAndInflowDownloaded(
origin.getId(), origin.getTargetFilePath(), origin.isForceReDownload(), true)
) {
// Will be removed when the complete message is received in #update
return;
}
//启动任务
final boolean succeed = FileDownloadServiceProxy.getImpl().
start(
origin.getUrl(),
origin.getPath(),
origin.isPathAsDirectory(),
origin.getCallbackProgressTimes(), origin.getCallbackProgressMinInterval(),
origin.getAutoRetryTimes(),
origin.isForceReDownload(),
mTask.getHeader(),
origin.isWifiRequired());
//如果任务是暂停的话那么这里就会执行暂停操作
if (mStatus == FileDownloadStatus.paused) {
FileDownloadLog.w(this, "High concurrent cause, this task %d will be paused," +
"because of the status is paused, so the pause action must be applied", getId());
if (succeed) {
FileDownloadServiceProxy.getImpl().pause(getId());
}
return;
}
if (!succeed) {//如果任务启动失败
//noinspection StatementWithEmptyBody
//如果启动服务失败则进入如下代码
if (!lostConnectedHandler.dispatchTaskStart(runningTask)) {
final MessageSnapshot snapshot = prepareErrorMessage(
new RuntimeException("Occur Unknown Error, when request to start" +
" maybe some problem in binder, maybe the process was killed in " +
"unexpected."));
//任务列表没有当前任务则添加
if (FileDownloadList.getImpl().isNotContains(runningTask)) {
lostConnectedHandler.taskWorkFine(runningTask);
FileDownloadList.getImpl().add(runningTask);
}
//有的话就删除,说明执行任务出错了
FileDownloadList.getImpl().remove(runningTask, snapshot);
} else {
// the FileDownload Service host process was killed when request stating and it
// will be restarted by LostServiceConnectedHandler.
}
} else {
//任务执行成功
lostConnectedHandler.taskWorkFine(runningTask);
}
} catch (Throwable e) {
e.printStackTrace();
FileDownloadList.getImpl().remove(runningTask, prepareErrorMessage(e));
}
}
上面的代码可以看到我已经注释的非常清楚了,像服务的启动,我这里就不会展开讲了,因为挺简单的。FileDownloadHelper#inspectAndInflowDownloaded()
方法我们看到没有注释,有机会会拿出来讲下,我们先来看看主线逻辑,我们来看看启动任务做了什么工作FileDownloadServiceProxy#start()
:
@Override
public boolean start(String url, String path, boolean pathAsDirectory, int callbackProgressTimes,
int callbackProgressMinIntervalMillis,
int autoRetryTimes, boolean forceReDownload, FileDownloadHeader header,
boolean isWifiRequired) {
return handler.start(url, path, pathAsDirectory, callbackProgressTimes,
callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,
isWifiRequired);
}
这里的handler是在FileDownloadServiceProxy
的构造函数里面初始化的,这里我们可以来看下:
private FileDownloadServiceProxy() {
handler = FileDownloadProperties.getImpl().PROCESS_NON_SEPARATE ?
new FileDownloadServiceSharedTransmit() :
new FileDownloadServiceUIGuard();
}
我们看到这里会在FileDownloadProperties
类中去获取PROCESS_NON_SEPARATE
标志。如果你去看FileDownloadProperties
构造函数就会知道,这些值是从filedownloader.properties
这个文件配置的,这里还有很多其他的标志,这里的PROCESS_NON_SEPARATE
是为了标识是否下载服务是在单独的进程中的,我们这里就默认设置为在单独进程中下载,在单独的进程中执行下载服务的好处是可以减少应用进程占用的内存,且使应用更加稳定。所以当PROCESS_NON_SEPARATE
为false的情况下,我们的handler会是FileDownloadServiceUIGuard
类实例,所以这里的start()
方法就会是FileDownloadServiceUIGuard#start()
:
public boolean start(final String url, final String path, final boolean pathAsDirectory,
final int callbackProgressTimes,
final int callbackProgressMinIntervalMillis,
final int autoRetryTimes, final boolean forceReDownload,
final FileDownloadHeader header, final boolean isWifiRequired) {
if (!isConnected()) {
return DownloadServiceNotConnectedHelper.start(url, path, pathAsDirectory);
}
try {
getService().start(url, path, pathAsDirectory, callbackProgressTimes,
callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,
isWifiRequired);
} catch (RemoteException e) {
e.printStackTrace();
return false;
}
return true;
}
这里的getService()
方法返回的是远程的服务代理,因为这里是跨进程的,所以远程的服务是SeparateProcessService
,为什么说这个方法获取到的是远程的服务代理呢?因为是FileDownloadServiceUIGuard
实现了ServiceConnection
,所以在绑定远程服务的时候会回调onServiceConnected()
方法,我们在这里可以看到:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
this.service = asInterface(service);
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "onServiceConnected %s %s", name, this.service);
}
try {
registerCallback(this.service, this.callback);
} catch (RemoteException e) {
e.printStackTrace();
}
@SuppressWarnings("unchecked") final List<Runnable> runnableList =
(List<Runnable>) connectedRunnableList.clone();
connectedRunnableList.clear();
for (Runnable runnable : runnableList) {
runnable.run();
}
FileDownloadEventPool.getImpl().
asyncPublishInNewThread(new DownloadServiceConnectChangedEvent(
DownloadServiceConnectChangedEvent.ConnectStatus.connected, serviceClass));
}
可以看到第一句就是赋值service,所以我们直接看远程服务SeparateProcessService#start()
方法,这个SeparateProcessService
是继承FileDownloadService
类的,它本身是一个空实现,我们来看看FileDownloadService
中的start()方法,熟悉AIDL的人都知道,这些实现都在Binder实体中实现,我们来看下onBind
方法:
@Override
public IBinder onBind(Intent intent) {
return handler.onBind(intent);
}
这里又调用了handler
的onBind
方法,我们看下这里的handler
是个什么东西?
if (FileDownloadProperties.getImpl().PROCESS_NON_SEPARATE) {
handler = new FDServiceSharedHandler(new WeakReference<>(this), manager);
} else {
handler = new FDServiceSeparateHandler(new WeakReference<>(this), manager);
}
可以看到,我们这里PROCESS_NON_SEPARATE
是为false的,所以我们的handler
是FDServiceSeparateHandler
实例。所以我们看FDServiceSeparateHandler#onBind()
方法:
@Override
public IBinder onBind(Intent intent) {
return this;
}
public class FDServiceSeparateHandler extends IFileDownloadIPCService.Stub
implements MessageSnapshotFlow.MessageReceiver, IFileDownloadServiceHandler {
}
我们看到这里onBind
方法返回this,FDServiceSeparateHandler
实现了IFileDownloadIPCService.Stub
,所以FDServiceSeparateHandler
就是Binder实体,我们看这个类里面的start()
方法:
@Override
public void start(String url, String path, boolean pathAsDirectory, int callbackProgressTimes,
int callbackProgressMinIntervalMillis, int autoRetryTimes, boolean forceReDownload,
FileDownloadHeader header, boolean isWifiRequired) throws RemoteException {
downloadManager.start(url, path, pathAsDirectory, callbackProgressTimes,
callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,
isWifiRequired);
}
这里调用了downloadManager
的start方法,这里的downloadManager
是FileDownloadManager
实例,所以程序会调用FileDownloadManager#start()
方法。
4.FileDownloadManager start
这里就是真正开始启动下载任务了,我们完整地看下代码:
// synchronize for safe: check downloading, check resume, update data, execute runnable
public synchronized void start(final String url, final String path, final boolean pathAsDirectory,
final int callbackProgressTimes,
final int callbackProgressMinIntervalMillis,
final int autoRetryTimes, final boolean forceReDownload,
final FileDownloadHeader header, final boolean isWifiRequired) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "request start the task with url(%s) path(%s) isDirectory(%B)",
url, path, pathAsDirectory);
}
//根据请求链接,文件存储路径,已经一个boolean值来生成一次请求的唯一id
final int id = FileDownloadUtils.generateId(url, path, pathAsDirectory);
//从数据库中获取这个id对应的请求,封装在FileDownloadModel 中,因为有可能前面已经有下载过了,只是由于不可抗性终止了
FileDownloadModel model = mDatabase.find(id);
List<ConnectionModel> dirConnectionModelList = null;
//如果pathAsDirectory 为false也就是说是文件,而且获取到的model为null,那么进入如下逻辑
if (!pathAsDirectory && model == null) {
// try dir data.
//根据这个文件的上级目录来生成一个唯一id
final int dirCaseId = FileDownloadUtils.generateId(url, FileDownloadUtils.getParent(path),
true);
//寻找这个id对应的model
model = mDatabase.find(dirCaseId);
if (model != null && path.equals(model.getTargetFilePath())) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "task[%d] find model by dirCaseId[%d]", id, dirCaseId);
}
//获取该id下的所有连接,即对应的FileDownloadModel集合
dirConnectionModelList = mDatabase.findConnectionModel(dirCaseId);
}
}
if (FileDownloadHelper.inspectAndInflowDownloading(id, model, this, true)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "has already started download %d", id);
}
return;
}
//获取文件路径,没有则生成一个
final String targetFilePath = model != null ? model.getTargetFilePath() :
FileDownloadUtils.getTargetFilePath(path, pathAsDirectory, null);
if (FileDownloadHelper.inspectAndInflowDownloaded(id, targetFilePath, forceReDownload,
true)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "has already completed downloading %d", id);
}
return;
}
//获取文件下载了多少字节,因为有可能之前下了一半断掉了,然后获取暂存文件路径
final long sofar = model != null ? model.getSoFar() : 0;
final String tempFilePath = model != null ? model.getTempFilePath() :
FileDownloadUtils.getTempPath(targetFilePath);
if (FileDownloadHelper.inspectAndInflowConflictPath(id, sofar, tempFilePath, targetFilePath,
this)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "there is an another task with the same target-file-path %d %s",
id, targetFilePath);
// because of the file is dirty for this task.
if (model != null) {
mDatabase.remove(id);
mDatabase.removeConnections(id);
}
}
return;
}
// real start
// - create model
//创建连接model,如果数据库已经存在这个model,而且状态是下面状态中的一种,
//那么说明真的是意外退出了,则根据下面情况看要不要更新数据库,否则就创建一个新的model请求
boolean needUpdate2DB;
if (model != null &&
(model.getStatus() == FileDownloadStatus.paused ||
model.getStatus() == FileDownloadStatus.error ||
model.getStatus() == FileDownloadStatus.pending ||
model.getStatus() == FileDownloadStatus.started ||
model.getStatus() == FileDownloadStatus.connected) // FileDownloadRunnable invoke
// #isBreakpointAvailable to determine whether it is really invalid.
) {
if (model.getId() != id) {
// in try dir case.
mDatabase.remove(model.getId());
mDatabase.removeConnections(model.getId());
model.setId(id);
model.setPath(path, pathAsDirectory);
if (dirConnectionModelList != null) {
for (ConnectionModel connectionModel : dirConnectionModelList) {
connectionModel.setId(id);
mDatabase.insertConnectionModel(connectionModel);
}
}
needUpdate2DB = true;
} else {
if (!TextUtils.equals(url, model.getUrl())) {
// for cover the case of reusing the downloaded processing with the different url( using with idGenerator ).
model.setUrl(url);
needUpdate2DB = true;
} else {
needUpdate2DB = false;
}
}
} else {
if (model == null) {
model = new FileDownloadModel();
}
model.setUrl(url);
model.setPath(path, pathAsDirectory);
model.setId(id);
model.setSoFar(0);
model.setTotal(0);
model.setStatus(FileDownloadStatus.pending);
model.setConnectionCount(1);
needUpdate2DB = true;
}
// - update model to db
if (needUpdate2DB) {
mDatabase.update(model);
}
final DownloadLaunchRunnable.Builder builder = new DownloadLaunchRunnable.Builder();
//利用建造者模式创建DownloadLaunchRunnable 对象
final DownloadLaunchRunnable runnable =
builder.setModel(model)
.setHeader(header)
.setThreadPoolMonitor(this)
.setMinIntervalMillis(callbackProgressMinIntervalMillis)
.setCallbackProgressMaxCount(callbackProgressTimes)
.setForceReDownload(forceReDownload)
.setWifiRequired(isWifiRequired)
.setMaxRetryTimes(autoRetryTimes)
.build();
// - execute
//执行这个Runnable
mThreadPool.execute(runnable);
}
我们看到上面的方法主要作用就是根据url,path与是否是目录来标识一个请求,如果符合意外退出的情况,那么会对SoFar(文件已经下载了多少)这个值进行恢复。最后重新赋值给DownloadLaunchRunnable
对象,进行执行,DownloadLaunchRunnable
是一个Runnable对象,所以会执行他的run()
方法:
@Override
public void run() {
try {
//设置线程优先级为后台,这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// status checkout
//检查状态
if (model.getStatus() != FileDownloadStatus.pending) {
if (model.getStatus() == FileDownloadStatus.paused) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "High concurrent cause, start runnable but " +
"already paused %d", model.getId());
}
} else {
onError(new RuntimeException(
FileDownloadUtils.formatString("Task[%d] can't start the download" +
" runnable, because its status is %d not %d",
model.getId(), model.getStatus(), FileDownloadStatus.pending)));
}
return;
}
//更新model的状态为启动状态
if (!paused) {
statusCallback.onStartThread();
}
do {
//如果是暂停状态则返回不执行,如日志所说,高并发可能导致这个结果
if (paused) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "High concurrent cause, start runnable but " +
"already paused %d", model.getId());
}
return;
}
FileDownloadConnection connection = null;
try {
// 1. connect
//检查权限和是否需要wifi才能下载条件
checkupBeforeConnect();
// the first connection is for: 1. etag verify; 2. first connect.
final List<ConnectionModel> connectionOnDBList = database.findConnectionModel(model.getId());
//检查是否恢复之前的连接,如果是多线程连接且不支持seek的情况是不支持
//断点恢复的,如果支持断点恢复,在多线程连接的情况下会把每个连接下载了
//多少计算赋值给model的SoFar,
final ConnectionProfile connectionProfile = buildFirstConnectProfile(connectionOnDBList);
final ConnectTask.Builder build = new ConnectTask.Builder();
//创建新的连接
final ConnectTask firstConnectionTask = build.setDownloadId(model.getId())
.setUrl(model.getUrl())
.setEtag(model.getETag())
.setHeader(userRequestHeader)
.setConnectionProfile(connectionProfile)
.build();
//调用FileDownloadUrlConnection进行连接请求,获取文件长度
connection = firstConnectionTask.connect();
//会根据ETag来判断远端的文件是否有改变,改变的话就是从头开始下载,不然就获取文件总长度
handleFirstConnected(firstConnectionTask.getRequestHeader(),
firstConnectionTask, connection);
if (paused) {
model.setStatus(FileDownloadStatus.paused);
return;
}
// 2. fetch
//检查是否有另外一个url请求任务跟这个任务目录存储路径一样,
//一样的话判断这个url下载的文件是否存在,如果其他进程正在下载这个文件,
//那么这里请求就停止,如果其他进程下载文件已经停止了,那么这里就从断点
//进行恢复下载
checkupBeforeFetch();
final long totalLength = model.getTotal();
// pre-allocate if need.
//预先分配文件的大小
handlePreAllocate(totalLength, model.getTempFilePath());
final int connectionCount;
// start fetching
//是否支持多线程即多个连接下载
if (isMultiConnectionAvailable()) {
if (isResumeAvailableOnDB) {
connectionCount = model.getConnectionCount();
} else {
connectionCount = CustomComponentHolder.getImpl()
.determineConnectionCount(model.getId(), model.getUrl(), model.getPath(), totalLength);
}
} else {
connectionCount = 1;
}
if (connectionCount <= 0) {
throw new IllegalAccessException(FileDownloadUtils
.formatString("invalid connection count %d, the connection count" +
" must be larger than 0", connection));
}
if (paused) {
model.setStatus(FileDownloadStatus.paused);
return;
}
isSingleConnection = connectionCount == 1;
if (isSingleConnection) {
// single connection
//单个子线程下载
fetchWithSingleConnection(firstConnectionTask.getProfile(), connection);
} else {
if (connection != null) {
connection.ending();
connection = null;
}
// multiple connection
statusCallback.onMultiConnection();
if (isResumeAvailableOnDB) {
//恢复多线程下载
fetchWithMultipleConnectionFromResume(connectionCount, connectionOnDBList);
} else {
//从头开始多线程下载
fetchWithMultipleConnectionFromBeginning(totalLength, connectionCount);
}
}
} catch (IOException | IllegalAccessException | InterruptedException | IllegalArgumentException | FileDownloadGiveUpRetryException e) {
if (isRetry(e)) {
onRetry(e, 0);
continue;
} else {
onError(e);
}
} catch (DiscardSafely discardSafely) {
return;
} catch (RetryDirectly retryDirectly) {
model.setStatus(FileDownloadStatus.retry);
continue;
} finally {
if (connection != null) connection.ending();
}
break;
} while (true);
} finally {
statusCallback.discardAllMessage();
if (paused) {
statusCallback.onPausedDirectly();
} else if (error) {
statusCallback.onErrorDirectly(errorException);
} else {
try {
statusCallback.onCompletedDirectly();
} catch (IOException e) {
statusCallback.onErrorDirectly(e);
}
}
alive.set(false);
}
}
我们看到这个方法就是下载的主要方法了,流程注释已经写得很清楚了,就是里面没有展开写,不然本篇文章的篇幅要很长了,到这里我们已经讲完请求的大体流程了,这里明确下一个请求对应一个FileDownloadModel
实体,每个请求下面又可以有多个ConnectionModel
实体,每个ConnectionModel
实体对应一条请求线程。当然这里面的FileDownloadHelper#inspectAndInflowDownloaded()
方法没有讲,如果大家看不懂可以给我留言,我会解答。
总结:这个下载框架整体来说思维还是不错的,希望里面的编程思想会有帮助到大家,在以后自己编写框架的过程中能有所借鉴,最后祝大家源码之路愉快哈。
网友评论