某学姐

Android Female Developer, Technology Fan, Reader.

Android学习笔记——okdownload

2018-09-20 | Comments

源码: https://github.com/lingochamp/okdownload

wiki: https://github.com/lingochamp/okdownload/wiki

整个代码风格和okhttp有点相似。

关于这个源码的分析,我结合之前写的一篇涉及“关于模块设计”主题的文章 学习方法总结 来分析。

(1)明确实现的功能

参考1:okhttp对外接口调用:

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
      .url(url)
      .build();

Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        ...
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ...
    }
});

参考2: volley对外接口调用:

RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
        new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
    }
});

queue.add(stringRequest);

okdownload的wiki文档里,关于各种不同情况的下载,对外接口并不是统一的,我觉得OnDownload这个类应该是一个对外类。

// 在源码基础上略有修改
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();
// 异步
OkDownload.with().enqueue(task, downloadlistener);
// 同步
OkDownload.with().execute(task, downloadlistener);    

(2)分析实现这个功能需要哪些角色

OkDownload用到了Builder模式。涉及如下角色:

// 下载分发器
private DownloadDispatcher downloadDispatcher;
// 回调分发器
private CallbackDispatcher callbackDispatcher;
// 下载存储类
private DownloadStore downloadStore;
// 下载连接
private DownloadConnection.Factory connectionFactory;
// 文件处理
private ProcessFileStrategy processFileStrategy;
// 下载策略
private DownloadStrategy downloadStrategy;
// 下载输出流
private DownloadOutputStream.Factory outputStreamFactory;

下载分发器 DownloadDispatcher

private final List<DownloadCall> readyAsyncCalls;
private final List<DownloadCall> runningAsyncCalls;
private final List<DownloadCall> runningSyncCalls;
private final List<DownloadCall> finishingCalls;

private volatile ExecutorService executorService;
private int maxParallelRunningCount = 5;

public void enqueue(DownloadTask[] tasks);
public void enqueue(DownloadTask task);
public void execute(DownloadTask task);

public boolean cancel(IdentifiedTask task);
public void cancel(IdentifiedTask[] tasks);
public boolean cancel(int id);
public void cancelAll();

回调分发器 CallbackDispatcher

private final DownloadListener transmit;
private final Handler uiHandler;

public DownloadListener dispatch();

public class DefaultTransmitListener implements interface DownloadListener {
    void taskStart(DownloadTask task);
    void connectTrialStart(DownloadTask task, Map<String, List<String>> requestHeaderFields);
    void connectTrialEnd(DownloadTask task, int responseCode, Map<String, List<String>> responseHeaderFields);
    void downloadFromBeginning(DownloadTask task, BreakpointInfo info, ResumeFailedCause cause);
    void downloadFromBreakpoint(DownloadTask task, BreakpointInfo info);
    void connectStart(DownloadTask task, int blockIndex, Map<String, List<String>> requestHeaderFields);
    void connectEnd(DownloadTask task, int blockIndex, int responseCode, Map<String, List<String>> responseHeaderFields);
    void fetchStart(DownloadTask task, int blockIndex, long contentLength);
    void fetchProgress(DownloadTask task, int blockIndex, long increaseBytes);
    void fetchEnd(DownloadTask task, int blockIndex, long contentLength);
    void taskEnd(DownloadTask task, EndCause cause, Exception realCause);
}

下载存储类 DownloadStore

默认实现类是BreakpointStoreOnSQLite
protected final BreakpointSQLiteHelper helper;
protected final BreakpointStoreOnCache onCache;

BreakpointInfo get(int id);
BreakpointInfo createAndInsert(DownloadTask task);
int findOrCreateId(DownloadTask task);
boolean update(BreakpointInfo breakpointInfo);
void remove(int id);

下载连接 DownloadConnection,使用简单工厂模式

void addHeader(String name, String value);
boolean setRequestMethod(@NonNull String method);
Connected execute();
void release();

interface Factory {
    DownloadConnection create(String url) throws IOException;
}

文件处理 ProcessFileStrategy

public MultiPointOutputStream createProcessStream(@NonNull DownloadTask task,
                                                           @NonNull BreakpointInfo info,
                                                           @NonNull DownloadStore store) {
    return new MultiPointOutputStream(task, info, store);
}

public void completeProcessStream(@NonNull MultiPointOutputStream processOutputStream,
                                  @NonNull DownloadTask task) {
}

public void discardProcess(@NonNull DownloadTask task);

而MultiPointOutputStream是支持分段处理的输出流,FileChannel支持seek
public void write(int blockIndex, byte[] bytes, int length);
public void cancel();
public void done(int blockIndex);
public void cancelAsync();

下载策略 DownloadStrategy

// 1 connection: [0, 1MB)
private static final long ONE_CONNECTION_UPPER_LIMIT = 1024 * 1024; // 1MiB
// 2 connection: [1MB, 5MB)
private static final long TWO_CONNECTION_UPPER_LIMIT = 5 * 1024 * 1024; // 5MiB
// 3 connection: [5MB, 50MB)
private static final long THREE_CONNECTION_UPPER_LIMIT = 50 * 1024 * 1024; // 50MiB
// 4 connection: [50MB, 100MB)
private static final long FOUR_CONNECTION_UPPER_LIMIT = 100 * 1024 * 1024; // 100MiB

public int determineBlockCount(@NonNull DownloadTask task, long totalLength);
public boolean isUseMultiBlock(final boolean isAcceptRange);

下载输出流 DownloadOutputStream,使用简单工厂模式

void write(byte[] b, int off, int len);
void close();
void flushAndSync();
void seek(long offset);
void setLength(long newLength);
interface Factory {
    DownloadOutputStream create(Context context, File file, int flushBufferSize);
    DownloadOutputStream create(Context context, Uri uri, int flushBufferSize);
    boolean supportSeek();
}

默认实现类DownloadUriOutputStream,成员变量
private final FileChannel channel;
private final ParcelFileDescriptor pdf;
private final BufferedOutputStream out;
private final FileOutputStream fos;

(3)理清各角色之间的关系

(4)细化各角色的具体工作

DownloadConnection:使用的okhttp

DownloadStore:内存存储和db

支持分段下载,分段规则由DownloadStrategy决定

public class BreakpointInfo {
    private final int id;
    private final String url;
    private String etag;
    private File targetFile;
    private final List<BlockInfo> blockInfoList;
}

public class BlockInfo {
    @IntRange(from = 0)
    private final long startOffset;
    @IntRange(from = 0)
    private final long contentLength;
    private final AtomicLong currentOffset;
}
本文原文发自 某学姐, 转载请保留出处, 谢谢.

Comments