okdownload介绍


OkDownload是一个android下载框架,FileDownloader的升级版本,也称FileDownloader2;是一个支持多线程,多任务,断点续传,可靠,灵活,高性能以及强大的下载引擎,由于版本之间有代码差异,我们基于1.0.2版本做介绍。

 

 

 

 1.  对比FileDownloader的优势

 

 

 

  •  单元测试覆盖率很高,从而保证框架的可靠性。
  • 简单的接口设计。
  • 支持任务优先级。
  • Uri文件转存储输出流。
  • 核心类库更加单一和轻量级。
  • 更灵活的回调机制和侦听器。
  • 更灵活地扩展OkDownload的每个部分。
  • 在不降低性能的情况下,更少的线程可以执行相同的操作。
  • 文件IO线程池和网络IO线程池分开。
  • 如果无法从响应头中找到,从URL中获取自动文件名。
  • 取消和开始是非常有效的,特别是对于大量的任务,有大量的优化。

 

 

2.  如何引入

 

 

// core
com.liulishuo.okdownload:okdownload:{latest_version}
// provide sqlite to store breakpoints
com.liulishuo.okdownload:sqlite:{latest_version}
// provide okhttp to connect to backend
// and then please import okhttp dependencies by yourself
com.liulishuo.okdownload:okhttp:{latest_version}


目前稳定版本是1.0.2,导入时候,请把{latest_version}替换为:1.0.2

 

 

 

3.如何使用

 

 

 

 1)开始和取消任务

单文件下载

DownloadTask task = new DownloadTask.Builder(url, parentFile)
         .setFilename(filename)
         // the minimal interval millisecond for callback progress
         .setMinIntervalMillisCallbackProcess(30)
         // do re-download even if the task has already been completed in the past.
         .setPassIfAlreadyCompleted(false)
         .build();


task.enqueue(listener);


// cancel
task.cancel();


// execute task synchronized
task.execute(listener);

 


多文件下载

 

 

final DownloadTask[] tasks = new DownloadTask[2];
tasks[0] = new DownloadTask.Builder("url1", "path", "filename1").build();
tasks[1] = new DownloadTask.Builder("url2", "path", "filename1").build();
DownloadTask.enqueue(tasks, listener);





多文件下载只能有一个开始下载,其他等待

 

DownloadContext.Builder builder = new DownloadContext.QueueSet()
        .setParentPathFile(parentFile)
        .setMinIntervalMillisCallbackProcess(150)
        .commit();
builder.bind(url1);
builder.bind(url2).addTag(key, value);
builder.bind(url3).setTag(tag);
builder.setListener(contextListener);


DownloadTask task = new DownloadTask.Builder(url4, parentFile)
        .setPriority(10).build();
builder.bindSetTask(task);


DownloadContext context = builder.build();


context.startOnParallel(listener);


// stop
context.stop();

 

 

 

 

2)状态监听

通常使用DownloadListener来获取下载进度通知


在没有DownloadListener情况下,获取任务状态

 

 

 

 

 

Status status = StatusUtil.getStatus(task)


status = StatusUtil.getStatus(url, parentPath, null);
status = StatusUtil.getStatus(url, parentPath, filename);


boolean isCompleted = StatusUtil.isCompleted(task);
isCompleted = StatusUtil.isCompleted(url, parentPath, null);
isCompleted = StatusUtil.isCompleted(url, parentPath, filename);


Status completedOrUnknown = StatusUtil.isCompletedOrUnknown(task);


// If you set tag, so just get tag
task.getTag();
task.getTag(xxx);



在没有DownloadListener情况下,获取断点信息

 

 

 

 

 

// Note: the info will be deleted since task is completed download for data health lifecycle
BreakpointInfo info = OkDownload.with().breakpointStore().get(it);


info = StatusUtil.getCurrentInfo(url, parentPath, null);
info = StatusUtil.getCurrentInfo(url, parentPath, filename);


// Since okdownload v1.0.1-SNAPSHOT
// the info reference will be cached on the task refrence even if the task is completed download.
info = task.getInfo();

 

 

 

 

3) 监听类型


 

 

 

 

 

 

 

4.高级功能(可选)

 1)调试OkDownload

请通过Util.enableConsoleLog()在控制台打印上启用日志,您也可以通过Util.setLogger(Logger)设置自己的日志记录器

 

2)全局控制

 

 

 

OkDownload.with().setMonitor(monitor);


DownloadDispatcher.setMaxParallelRunningCount(3);


RemitStoreOnSQLite.setRemitToDBDelayMillis(3000);


OkDownload.with().downloadDispatcher().cancelAll();


OkDownload.with().breakpointStore().remove(taskId);

 

 

 

 

 3)组件注入

如果您想注入您的组件,请在使用OkDownload之前调用以下方法:

 

 

 

 

 

 

 

	OkDownload.Builder builder = new OkDownload.Builder(context)
        .downloadStore(downloadStore)
        .callbackDispatcher(callbackDispatcher)
        .downloadDispatcher(downloadDispatcher)
        .connectionFactory(connectionFactory)
        .outputStreamFactory(outputStreamFactory)
        .downloadStrategy(downloadStrategy)
        .processFileStrategy(processFileStrategy)
        .monitor(monitor);


OkDownload.setSingletonInstance(builder.build());

 

 

 4)自定义okdownload策略

 


注意只能执行一次,否则报错,建议放到OkDownloadProvider的onCreate做全局设置:

 

 

 

 

 

public class OkDownloadProvider extends ContentProvider {
    @SuppressLint("StaticFieldLeak") static Context context;


    @Override
    public boolean onCreate() {
        context = getContext();
        OkDownload.setSingletonInstance(new OkDownload.Builder(context).downloadStrategy(new DownloadStrategy(){
            @Override
            public int determineBlockCount(@NonNull DownloadTask task, long totalLength) {
                return 1;
            }
        }).build());
        return true;
    }
}

 

 

 

 5)设置多个监听

 

 

 

DownloadListener listener1 = new DownloadListener1();
DownloadListener listener2 = new DownloadListener2();


DownloadListener combinedListener = new DownloadListenerBunch.Builder()
                   .append(listener1)
                   .append(listener2)
                   .build();


DownloadTask task = new DownloadTask.build(url, file).build();
task.enqueue(combinedListener);

 

 

 6)动态更改监听

 

 

// all attach or detach is based on the id of Task in fact. 
UnifiedListenerManager manager = new UnifiedListenerManager();


DownloadListener listener1 = new DownloadListener1();
DownloadListener listener2 = new DownloadListener2();
DownloadListener listener3 = new DownloadListener3();
DownloadListener listener4 = new DownloadListener4();


DownloadTask task = new DownloadTask.build(url, file).build();


manager.attachListener(task, listener1);
manager.attachListener(task, listener2);


manager.detachListener(task, listener2);


// all listeners added for this task will be removed when task is end.
manager.addAutoRemoveListenersWhenTaskEnd(task.getId());


// enqueue task to start.
manager.enqueueTaskWithUnifiedListener(task, listener3);


manager.attachListener(task, listener4);

 

 

 

7)自定义DownloadTask

 

 

 

DownloadTask.Builder builder = new DownloadTask.Builder(url, file);


// Set the minimum internal milliseconds of progress callbacks to 100ms.(default is 3000)
builder.setMinIntervalMillisCallbackProcess(100);


// set the priority of the task to 10, higher means less time to wait to download.(default is 0)
builder.setPriority(10);


// set the read buffer to 8192 bytes for the response input-stream.(default is 4096)
builder.setReadBufferSize(8192);


// set the flush buffer to 32768 bytes for the buffered output-stream.(default is 16384)
builder.setFlushBufferSize(32768);


// set this task allow using 5 connections to download data.
builder.setConnectionCount(5);


// build the task.
DownloadTask task = builder.build();

 

 

8)查询已经下载任务状态

 

 

    private void initTask() {
        final String filename = "single-test";
        final String url = "https://cdn.llscdn.com/yy/files/xs8qmxn8-lls-LLS-5.8-800-20171207-111607.apk";
        final File parentFile = DemoUtil.getParentFile(this);
        DownloadTask task = new DownloadTask.Builder(url, parentFile)
                .setFilename(filename)
                // the minimal interval millisecond for callback progress
                .setMinIntervalMillisCallbackProcess(16)
                // ignore the same task has already completed in the past.
                .setPassIfAlreadyCompleted(false)
                .build();
    }
	
	
private void initStatus(TextView statusTv, ProgressBar progressBar) {
		//获取下载状态
        final StatusUtil.Status status = StatusUtil.getStatus(task);
        if (status == StatusUtil.Status.COMPLETED) {
            progressBar.setProgress(progressBar.getMax());
        }


        statusTv.setText(status.toString());
		//获取断点下载信息
        final BreakpointInfo info = StatusUtil.getCurrentInfo(task);
        if (info != null) {
            Log.d(TAG, "init status with: " + info.toString());


            DemoUtil.calcProgressToView(progressBar, info.getTotalOffset(), info.getTotalLength());
        }
    }

 

 

 

 

 9)DownloadTask的Builder方法介绍

 

 

 

 

//在获取资源长度后,设置是否需要为文件预分配长度
setPreAllocateLength(boolean preAllocateLength) 


//需要用几个线程来下载文件
setConnectionCount(@IntRange(from = 1) int connectionCount) 


//当下载没有提供文件名的是,使用服务器地址的文件名;如果已经提供文件名,这个属性被忽略
setFilenameFromResponse(@Nullable Boolean filenameFromResponse)


//是否在主线程通知调用者
setAutoCallbackToUIThread(boolean autoCallbackToUIThread) 


//通知调用者的频率,避免anr
setMinIntervalMillisCallbackProcess(int minIntervalMillisCallbackProcess) 


//设置请求头
setHeaderMapFields(Map> headerMapFields)


//追加请求头
addHeader(String key, String value)


//设置优先级,默认值是0,值越大下载优先级越高
setPriority(int priority)


//设置读取缓存区大小,默认4096
setReadBufferSize(int readBufferSize)


//设置写入缓存区大小,默认16384
setFlushBufferSize(int flushBufferSize)


//写入到文件的缓冲区大小,默认65536
setSyncBufferSize(int syncBufferSize)


//写入文件的最小时间间隔
setSyncBufferIntervalMillis(int syncBufferIntervalMillis)


//设置下载文件名
setFilename(String filename)


//如果文件已经下载完成,再次发起下载请求时,是否忽略下载,还是从头开始下载
setPassIfAlreadyCompleted(boolean passIfAlreadyCompleted)


//只允许wifi下载
setWifiRequired(boolean wifiRequired)

 

 

 

 

 10)DownloadContext使用

通过对DownloadTask的封装,支持多个下载任务并行和串行下载。


初始化DownloadContext

 

 

 

 

 

 

 

 

 

  public void initTasks(@NonNull Context context, @NonNull DownloadContextListener listener) {
        final DownloadContext.QueueSet set = new DownloadContext.QueueSet();
        final File parentFile = new File(DemoUtil.getParentFile(context), "queue");
        this.queueDir = parentFile;


        set.setParentPathFile(parentFile);
        set.setMinIntervalMillisCallbackProcess(200);


        final DownloadContext.Builder builder = set.commit();


        String url = "http://dldir1.qq.com/weixin/android/weixin6516android1120.apk";
        DownloadTask boundTask = builder.bind(url);
        TagUtil.saveTaskName(boundTask, "1. WeChat");


        url = "https://cdn.llscdn.com/yy/files/tkzpx40x-lls-LLS-5.7-785-20171108-111118.apk";
        boundTask = builder.bind(url);
        TagUtil.saveTaskName(boundTask, "2. LiuLiShuo");


        DownloadContext  context = builder.build();
		}

 

 





DownloadContext的start方法开始下载,listener是监听回调,isSerial表示是否用串行来下载

 

 

 

 

 

 

 

public void start(@Nullable final DownloadListener listener, boolean isSerial)

 

 

 


 11)DownloadSerialQueue使用

适用于多个文件串行下载,动态增加下载文件,一个一个下载。

 

 

 

 

DownloadSerialQueue serialQueue = new DownloadSerialQueue(commonListener);


serialQueue.enqueue(task1);
serialQueue.enqueue(task2);


//暂停下载
serialQueue.pause();
//开始下载
serialQueue.resume();


int workingTaskId = serialQueue.getWorkingTaskId();
int waitingTaskCount = serialQueue.getWaitingTaskCount();


DownloadTask[] discardTasks = serialQueue.shutdown();

 

 

 

 

 

 

 5.源码分析

 

 

 

 

 

 

 

 

初始化流程

 


我们从单一文件下载开始分析,进入com.liulishuo.okdownload.sample.SingleActivity,请看initTask方法,是获取下载任务参数构建的简单例子,我们把.setFilename(filename)注释掉,方便分析后面如何从服务器获取文件名的流程。


构建一个下载任务的基础参数需要下载url和文件存储目录,其它参数可选。

 

 

 

 

 

    private void initTask() {
//        final String filename = "single-test";
        final String url =
                "https://cdn.llscdn.com/yy/files/xs8qmxn8-lls-LLS-5.8-800-20171207-111607.apk";
        final File parentFile = DemoUtil.getParentFile(this);
        task = new DownloadTask.Builder(url, parentFile)
//                .setFilename(filename)
                // the minimal interval millisecond for callback progress
                .setMinIntervalMillisCallbackProcess(16)
                // ignore the same task has already completed in the past.
                .setPassIfAlreadyCompleted(false)
                .build();
    }

 




接着看initStatus方法,该方法用于查询下载任务的状态和断点下载信息。

 

 

    private void initStatus(TextView statusTv, ProgressBar progressBar) {
        final StatusUtil.Status status = StatusUtil.getStatus(task);
        if (status == StatusUtil.Status.COMPLETED) {
            progressBar.setProgress(progressBar.getMax());
        }


        statusTv.setText(status.toString());
        final BreakpointInfo info = StatusUtil.getCurrentInfo(task);
        if (info != null) {
            Log.d(TAG, "init status with: " + info.toString());


            DemoUtil.calcProgressToView(progressBar, info.getTotalOffset(), info.getTotalLength());
        }
    }



进入StatusUtil.getStatus(task),里面会调用isCompletedOrUnknown方法从BreakpointStoreOnSQLite获取状态,BreakpointStoreOnSQLite里面包含了BreakpointSQLiteHelper(用于操作数据)和BreakpointStoreOnCache(用于做数据操作之前的数据缓存)。
根据task.getId()来获取断点信息,如果没有取到状态信息,接着从DownloadDispatcher从的readyAsyncCalls和runningSyncCalls和runningAsyncCalls查询状态,如果都查询不到,返回UNKNOWN。

 

    public static Status getStatus(@NonNull DownloadTask task) {


        final Status status = isCompletedOrUnknown(task);
        if (status == Status.COMPLETED) return Status.COMPLETED;


        final DownloadDispatcher dispatcher = OkDownload.with().downloadDispatcher();


        if (dispatcher.isPending(task)) return Status.PENDING;
        if (dispatcher.isRunning(task)) return Status.RUNNING;


        return status;
    }





进入StatusUtil.getCurrentInfo(task),先根据task查找id,再根据id查找断点信息,查询不到返回null。

 

    @Nullable public static BreakpointInfo getCurrentInfo(@NonNull DownloadTask task) {
        final BreakpointStore store = OkDownload.with().breakpointStore();
        final int id = store.findOrCreateId(task);


        final BreakpointInfo info = store.get(id);


        return info == null ? null : info.copy();
    }





到此,初始化流程完毕,界面如下:

 

 

 

 

 开始下载流程

点击STAR进入开始下载流程,会进入task.enqueue方法,并会根据下载情况回调给UI,这里的下载DownloadListener4WithSpeed还有很多类型,可以根据需要来设置监听器,具体看上面3.3片段的截图介绍。

 

 

 

 

 

private void startTask(final TextView statusTv, final ProgressBar progressBar,
                           final TextView actionTv) {


        task.enqueue(new DownloadListener4WithSpeed() {
            private long totalLength;
            private String readableTotalLength;


            @Override public void taskStart(@NonNull DownloadTask task) {
                statusTv.setText(R.string.task_start);
            }


            @Override
            public void infoReady(@NonNull DownloadTask task, @NonNull BreakpointInfo info,
                                  boolean fromBreakpoint,
                                  @NonNull Listener4SpeedAssistExtend.Listener4SpeedModel model) {
                statusTv.setText(R.string.info_ready);


                totalLength = info.getTotalLength();
                readableTotalLength = Util.humanReadableBytes(totalLength, true);
                DemoUtil.calcProgressToView(progressBar, info.getTotalOffset(), totalLength);
            }


            @Override public void connectStart(@NonNull DownloadTask task, int blockIndex,
                                               @NonNull Map> requestHeaders) {
                final String status = "Connect Start " + blockIndex;
                statusTv.setText(status);
            }


            @Override
            public void connectEnd(@NonNull DownloadTask task, int blockIndex, int responseCode,
                                   @NonNull Map> responseHeaders) {
                final String status = "Connect End " + blockIndex;
                statusTv.setText(status);
            }


            @Override
            public void progressBlock(@NonNull DownloadTask task, int blockIndex,
                                      long currentBlockOffset,
                                      @NonNull SpeedCalculator blockSpeed) {
            }


            @Override public void progress(@NonNull DownloadTask task, long currentOffset,
                                           @NonNull SpeedCalculator taskSpeed) {
                final String readableOffset = Util.humanReadableBytes(currentOffset, true);
                final String progressStatus = readableOffset + "/" + readableTotalLength;
                final String speed = taskSpeed.speed();
                final String progressStatusWithSpeed = progressStatus + "(" + speed + ")";


                statusTv.setText(progressStatusWithSpeed);
                DemoUtil.calcProgressToView(progressBar, currentOffset, totalLength);
            }


            @Override
            public void blockEnd(@NonNull DownloadTask task, int blockIndex, BlockInfo info,
                                 @NonNull SpeedCalculator blockSpeed) {
            }


            @Override public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause,
                                          @Nullable Exception realCause,
                                          @NonNull SpeedCalculator taskSpeed) {
                final String statusWithSpeed = cause.toString() + " " + taskSpeed.averageSpeed();
                statusTv.setText(statusWithSpeed);


                actionTv.setText(R.string.start);
                // mark
                task.setTag(null);
                if (cause == EndCause.COMPLETED) {
                    final String realMd5 = fileToMD5(task.getFile().getAbsolutePath());
                    if (!realMd5.equalsIgnoreCase("f836a37a5eee5dec0611ce15a76e8fd5")) {
                        Log.e(TAG, "file is wrong because of md5 is wrong " + realMd5);
                    }
                }
            }
        });
    }





进入DownloadTask的enqueue

 

    public void enqueue(DownloadListener listener) {
        this.listener = listener;
        OkDownload.with().downloadDispatcher().enqueue(this);
    }



我们这里重点分析一下OkDownload.with(),downloadDispatcher().enqueue(this); 稍后分析。
在首次build OkDownload对象的时候,会指定如下策略,这些也可以在build之前自定义设置自己的策略。
DownloadDispatcher:管理下载的机制,比如最大允许几个任务在后台运行,同步下载任务的处理。
CallbackDispatcher:回调给调用者的分发器,可以传入自定义Handler和DownloadListener(没有特殊需求无需自定义),也可以由系统默认转发给DownloadTask的listener,默认是在UI线程回调,如果需要在子线程回调,请在DownloadTask设置setAutoCallbackToUIThread的值为false。
DownloadStore:断点信息存储的位置,默认是数据库+内存缓存。
DownloadConnection.Factory:选择哪个网络通讯连接,默认是okhttp
DownloadOutputStream.Factory:构建文件输出流DownloadOutputStream,是否支持随机位置写入
ProcessFileStrategy:多文件写文件的方式,默认是根据每个线程写文件的不同位置,支持同时写入。
DownloadStrategy:下载策略,多大文件分几个线程
DownloadMonitor:下载状态监听,监听的接口请参见DownloadMonitor

 

 

 

 

 

public static OkDownload with() {
        if (singleton == null) {
            synchronized (OkDownload.class) {
                if (singleton == null) {
                    if (OkDownloadProvider.context == null) {
                        throw new IllegalStateException("context == null");
                    }
                    singleton = new Builder(OkDownloadProvider.context).build();
                }
            }
        }
        return singleton;
    }
	
	public OkDownload build() {
            //处理下载和等待任务的管理
            if (downloadDispatcher == null) {
                downloadDispatcher = new DownloadDispatcher();
            }


            //回调给UI的事件通知
            if (callbackDispatcher == null) {
                callbackDispatcher = new CallbackDispatcher();
            }


            //管理下载进度数据,存储数据库
            if (downloadStore == null) {
                downloadStore = Util.createDefaultDatabase(context);
            }


            //指定网络通讯实现,默认使用okhttp3
            if (connectionFactory == null) {
                connectionFactory = Util.createDefaultConnectionFactory();
            }


            //构建文件输出流DownloadOutputStream,是否支持随机位置写入
            if (outputStreamFactory == null) {
                outputStreamFactory = new DownloadUriOutputStream.Factory();
            }


            //文件处理策略,主要计算多个任务写文件的位置和文件写入
            if (processFileStrategy == null) {
                processFileStrategy = new ProcessFileStrategy();
            }


            //下载策略,多大文件分几个线程
            if (downloadStrategy == null) {
                downloadStrategy = new DownloadStrategy();
            }


            OkDownload okDownload = new OkDownload(context, downloadDispatcher, callbackDispatcher,
                    downloadStore, connectionFactory, outputStreamFactory, processFileStrategy,
                    downloadStrategy);


            okDownload.setMonitor(monitor);


            Util.d("OkDownload", "downloadStore[" + downloadStore + "] connectionFactory["
                    + connectionFactory);
            return okDownload;
        }

 

 




接着继续后面流程,进入DownloadDispatcher的enqueue,这里能看到maxParallelRunningCount默认值是5,表示最大允许有5个线程在下载,注意不是5个文件,因为一个文件可以分成几个线程去下载。

 

 

 

    public void enqueue(DownloadTask task) {
        skipProceedCallCount.incrementAndGet();
        enqueueLocked(task);
        skipProceedCallCount.decrementAndGet();
    }





进入DownloadDispatcher的enqueueLocked

 

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





进入DownloadDispatcher的enqueueIgnorePriority

 

    private synchronized void enqueueIgnorePriority(DownloadTask task) {
        final DownloadCall call = DownloadCall.create(task, true, store);
        //检查并行异步任务数量是否小于5
        if (runningAsyncSize() < maxParallelRunningCount) {
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            // priority
            //添加到待执行任务集合
            readyAsyncCalls.add(call);
        }
    }





executorService()的实现如下:

 

    synchronized ExecutorService executorService() {
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                    60, TimeUnit.SECONDS, new SynchronousQueue(),
                    Util.threadFactory("OkDownload Download", false));
        }


        return executorService;
    }



executorService().execute(call);方法会调用DownloadCall的run方法,DownloadCall类继承NamedRunnable,NamedRunnable里面定义run方法如下:

 

 

 

    public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
            //回调DownloadCall的execute方法
            execute();
        } catch (InterruptedException e) {
            //回调DownloadCall的canceled方法
            canceled(e);
        } finally {
            Thread.currentThread().setName(oldName);
            //回调DownloadCall的finished方法
            finished();
        }
    }

 





由于回调DownloadCall的execute方法,我们接着看,这个方法比较长,尽量在代码中添加注释,个别重要的方法后面会单独讲解。

 

 

 

 

public void execute() throws InterruptedException {
        currentThread = Thread.currentThread();


        //是否重试
        boolean retry;
        //重试次数
        int retryCount = 0;


        // 准备参数
        final OkDownload okDownload = OkDownload.with();
        final ProcessFileStrategy fileStrategy = okDownload.processFileStrategy();


        //开始任务,标记内存中任务状态并回调给调用者
        inspectTaskStart();
        do {
            // 0.下载前先检查基础参数,URL合法性校验
            if (task.getUrl().length() <= 0) {
                this.cache = new DownloadCache.PreError(
                        new IOException("unexpected url: " + task.getUrl()));
                break;
            }


            //中途取消任务
            if (canceled) break;


            // 1. 创建基础信息
            @NonNull final BreakpointInfo info;
            try {
                BreakpointInfo infoOnStore = store.get(task.getId());
                //如果数据库没有,则创建
                if (infoOnStore == null) {
                    info = store.createAndInsert(task);
                } else {
                    info = infoOnStore;
                }
                setInfoToTask(info);
            } catch (IOException e) {
                this.cache = new DownloadCache.PreError(e);
                break;
            }
            //中途取消任务
            if (canceled) break;


            // 准备缓存
            @NonNull final DownloadCache cache = createCache(info);
            this.cache = cache;


            // 2.检查服务器是否可以继续下载
            final BreakpointRemoteCheck remoteCheck = createRemoteCheck(info);
            try {
                //如果下载任务没有设置文件名,会在这里优先使用url的文件名,如果获取失败,则使用url的md5代替。
                remoteCheck.check();
            } catch (IOException e) {
                cache.catchException(e);
                break;
            }


            // 3. 等待要写入文件锁释放
            fileStrategy.getFileLock().waitForRelease(task.getFile().getAbsolutePath());


            // 4. 复用空闲断点信息 BreakpointInfo
            OkDownload.with().downloadStrategy()
                    .inspectAnotherSameInfo(task, info, remoteCheck.getInstanceLength());


            try {
                //服务器检查可以继续下载
                if (remoteCheck.isResumable()) {
                    // 5. 本地文件和断点数据完整性校验
                    final BreakpointLocalCheck localCheck = createLocalCheck(info,
                            remoteCheck.getInstanceLength());
                    //文件存在可恢复
                    //断点信息正确
                    //文件是否支持指定位置操作
                    localCheck.check();
                    if (localCheck.isDirty()) {
                        Util.d(TAG, "breakpoint invalid: download from beginning because of "
                                + "local check is dirty " + task.getId() + " " + localCheck);
                        //删除文件
                        fileStrategy.discardProcess(task);
                        // 6.计算任务分成几块去下载
                        assembleBlockAndCallbackFromBeginning(info, remoteCheck,
                                localCheck.getCauseOrThrow());
                    } else {
                        //回调给调用者
                        okDownload.callbackDispatcher().dispatch()
                                .downloadFromBreakpoint(task, info);
                    }
                } else {
                    Util.d(TAG, "breakpoint invalid: download from beginning because of "
                            + "remote check not resumable " + task.getId() + " " + remoteCheck);
                    //删除文件
                    fileStrategy.discardProcess(task);
                    // 6. 计算任务分成几块去下载
                    assembleBlockAndCallbackFromBeginning(info, remoteCheck,
                            remoteCheck.getCauseOrThrow());
                }
            } catch (IOException e) {
                cache.setUnknownError(e);
                break;
            }


            // 7. 开始下载
            start(cache, info);
            //中途取消任务
            if (canceled) break;


            // 8.下载失败重试
            if (cache.isPreconditionFailed()
                    && retryCount++ < MAX_COUNT_RETRY_FOR_PRECONDITION_FAILED) {
                store.remove(task.getId());
                retry = true;
            } else {
                retry = false;
            }
        } while (retry);


        // finish
        finishing = true;
        blockChainList.clear();


        final DownloadCache cache = this.cache;
        if (canceled || cache == null) return;


        final EndCause cause;
        Exception realCause = null;
        if (cache.isServerCanceled() || cache.isUnknownError()
                || cache.isPreconditionFailed()) {
            // error
            cause = EndCause.ERROR;
            realCause = cache.getRealCause();
        } else if (cache.isFileBusyAfterRun()) {
            cause = EndCause.FILE_BUSY;
        } else if (cache.isPreAllocateFailed()) {
            cause = EndCause.PRE_ALLOCATE_FAILED;
            realCause = cache.getRealCause();
        } else {
            cause = EndCause.COMPLETED;
        }
        //检查下载任务是否完成,删除断点信息,通知调用者
        inspectTaskEnd(cache, cause, realCause);

 





重点讲一下start(cache, info);

 

 

    void start(final DownloadCache cache, BreakpointInfo info) throws InterruptedException {
        final int blockCount = info.getBlockCount();
        final List blockChainList = new ArrayList<>(info.getBlockCount());
        final long totalLength = info.getTotalLength();
        //组装断点下载信息
        for (int i = 0; i < blockCount; i++) {
            final BlockInfo blockInfo = info.getBlock(i);
            //已经下载完毕
            if (Util.isCorrectFull(blockInfo.getCurrentOffset(), blockInfo.getContentLength())) {
                continue;
            }
            //如果某一块的信息有误,从头开始下载
            Util.resetBlockIfDirty(blockInfo);
            blockChainList.add(DownloadChain.createChain(i, task, info, cache, store));
        }
        //中途取消任务
        if (canceled) {
            return;
        }
        //开始多个断点下载
        startBlocks(blockChainList);
    }





进入startBlocks,会调用DownloadChain的run方法

 

    void startBlocks(List tasks) throws InterruptedException {
        ArrayList futures = new ArrayList<>(tasks.size());
        try {
            for (DownloadChain chain : tasks) {
                futures.add(submitChain(chain));
            }
            blockChainList.addAll(tasks);


            for (Future future : futures) {
                if (!future.isDone()) {
                    try {
                        future.get();
                    } catch (CancellationException | ExecutionException ignore) { }
                }
            }
        } catch (Throwable t) {
            for (Future future : futures) {
                future.cancel(true);
            }
            throw t;
        } finally {
            blockChainList.removeAll(tasks);
        }
    }



进入DownloadChain的run方法

 

    public void run() {
        if (isFinished()) {
            throw new IllegalAccessError("The chain has been finished!");
        }
        this.currentThread = Thread.currentThread();
        try {
            start();
        } catch (IOException ignored) {
            Util.w("--","ignored:"+ignored);
            // interrupt.
        } finally {
            finished.set(true);
            releaseConnectionAsync();
        }
    }





进入DownloadChain的start方法

 

void start() throws IOException {
        final CallbackDispatcher dispatcher = OkDownload.with().callbackDispatcher();
        // 发送请求拦截链
        final RetryInterceptor retryInterceptor = new RetryInterceptor();
        final BreakpointInterceptor breakpointInterceptor = new BreakpointInterceptor();
        connectInterceptorList.add(retryInterceptor);
        connectInterceptorList.add(breakpointInterceptor);
        connectInterceptorList.add(new RedirectInterceptor());
        connectInterceptorList.add(new HeaderInterceptor());
        connectInterceptorList.add(new CallServerInterceptor());


        connectIndex = 0;
        //处理请求拦截链
        final DownloadConnection.Connected connected = processConnect();
        // isPreconditionFailed || isUserCanceled || isServerCanceled || isUnknownError || isFileBusyAfterRun || isPreAllocateFailed;
        if (cache.isInterrupt()) {
            throw InterruptException.SIGNAL;
        }


        dispatcher.dispatch().fetchStart(task, blockIndex, getResponseContentLength());
        // 获取结果拦截链
        //负责写文件的拦截器
        final FetchDataInterceptor fetchDataInterceptor =
                new FetchDataInterceptor(blockIndex, connected.getInputStream(),
                        getOutputStream(), task);
        fetchInterceptorList.add(retryInterceptor);
        fetchInterceptorList.add(breakpointInterceptor);
        fetchInterceptorList.add(fetchDataInterceptor);


        fetchIndex = 0;
        final long totalFetchedBytes = processFetch();
        dispatcher.dispatch().fetchEnd(task, blockIndex, totalFetchedBytes);
    }





下次完成会回到DownloadCall的finished方法,取消回到DownloadCall的canceled方法,下载过程的状态会同步回调给调用者。






你可能感兴趣的:(okdownload,下载,多线程,断点续传,android)