流行框架源码解析(17)-英语流利说文件下载器源码解析

主目录见: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方法中会调用mTaskStarterstart()方法,从上面我们可以知道mTaskStarterDownloadTaskHunter实例,所以这里会调用DownloadTaskHunterstart()方法,这个方法现在是在子线程中执行的:

  @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);
    }

这里又调用了handleronBind方法,我们看下这里的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的,所以我们的handlerFDServiceSeparateHandler实例。所以我们看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方法,这里的downloadManagerFileDownloadManager实例,所以程序会调用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()方法没有讲,如果大家看不懂可以给我留言,我会解答。

总结:这个下载框架整体来说思维还是不错的,希望里面的编程思想会有帮助到大家,在以后自己编写框架的过程中能有所借鉴,最后祝大家源码之路愉快哈。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,587评论 18 399
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,322评论 0 12
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,358评论 0 17
  • 1.萤火虫之墓 珍惜眼前,珍惜当下,谁知道明天和意外,哪一个先来 ——《萤火虫之墓》 推荐理由:催泪神器,战争的残...
    林强自媒体阅读 1,610评论 7 73