日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

renameto 阻塞_打造简化版文件下载器

發(fā)布時間:2024/1/23 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 renameto 阻塞_打造简化版文件下载器 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一. 前言

Executors 是一種典型的生產(chǎn)者 - 消費者模式, java中的線程池是運用場景最多的并發(fā)框架,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池。線程池就是將線程進(jìn)行池化,需要運行任務(wù)時從池中拿一個線程來執(zhí)行,執(zhí)行完畢,線程放回池中。 在開發(fā)過程中,合理地使用線程池能夠帶來3個好處。第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控。

有沒有一種優(yōu)化后的線程池方案可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控呢?今天我就帶大家手把手寫一個簡化版的線程下載器

它有如下優(yōu)點支持文件下載監(jiān)聽成功,失敗,暫停,下載進(jìn)度回調(diào)

支持無限制添加任務(wù)到任務(wù)隊列

支持查看文件總下載大小

支持查看已經(jīng)下載的大小

支持查看下載成功后的目標(biāo)文件路徑

可以根據(jù)下載錯誤碼定位下載失敗原因

支持取消,暫停,開始下載任務(wù)

二. 關(guān)鍵技術(shù)

文件下載器不是很復(fù)雜,但是前提是你要對Executor一些參數(shù)要有簡單的了解下面我列了一個表格,對每個參數(shù)都詳細(xì)的介紹了一下

2.1 ThreadPoolExecutor 核心參數(shù)介紹參數(shù)名參數(shù)作用

2.1.1 RejectedExecutionHandler (飽和策閱)

線程池的飽和策略,當(dāng)阻塞隊列滿了,且沒有空閑的工作線程,如果繼續(xù)提交任務(wù),必須采取一種策略處理該任務(wù),線程池提供了4種策略: CallerRunsPolicy AbortPolicy DiscardPolicy DiscardOldestPolicy,API文檔也給大家列好了,不記得的可以翻一翻策閱名稱策閱作用

2.1.2 workQueue (飽和策閱)

workQueue必須是BlockingQueue阻塞隊列。當(dāng)線程池中的線程數(shù)超過它的corePoolSize的時候,線程會進(jìn)入阻塞隊列進(jìn)行阻塞等待。通過workQueue,線程池實現(xiàn)了阻塞功能

那么什么是阻塞隊列呢?

阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是向隊列里添加元素的線程,消費者是從隊列里取元素的線程。阻塞隊列就是生產(chǎn)者用來存放元素、消費者用來獲取元素的容器。Java提供的阻塞隊列如下:阻塞隊列名稱概念

阻塞隊列怎么實現(xiàn)增刪改查呢?

這里表格也給大家準(zhǔn)備好了:

2.2 如何創(chuàng)建一個線程池

所有的API介紹都一一和大家介紹了,那么我們該如何傳建線程池呢,創(chuàng)建線程池有五種方式: 定長線程池 , 可緩存的線程池 , 單線程的線程池還有長線程池,具體應(yīng)用場景如下:

2.2.1 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。

2.2.2 根據(jù)所需的并發(fā)數(shù)來動態(tài)創(chuàng)建和關(guān)閉線程。能夠合理的使用CPU進(jìn)行對任務(wù)進(jìn)行并發(fā)操作,所以適合使用在很耗時的任務(wù)。注意返回的是ForkJoinPool對象。

2.2.3 創(chuàng)建一個可緩存的線程池,可靈活回收空閑線程,若無可回收,則新建線程。

2.2.4 創(chuàng)建一個單線程的線程池

2.2.5. 創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。

2.3 ThreadPoolExecutor 源碼結(jié)構(gòu)

2.4 線程池的工作原理如果當(dāng)前運行的線程少于corePoolSize,則創(chuàng)建新線程來執(zhí)行任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)如果運行的線程等于或多于corePoolSize,則將任務(wù)加入BlockingQueue。如果無法將任務(wù)加入BlockingQueue(隊列已滿),則創(chuàng)建新的線程來處理任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)。如果創(chuàng)建新線程將使當(dāng)前運行的線程超出maximumPoolSize,任務(wù)將被拒絕,并調(diào)用RejectedExecutionHandler.rejectedExecution()方法。

整體來說: 在 線程池 的內(nèi)部,我們維護(hù)了一個阻塞隊列 workQueue 和一組工作線程,工作線程的個數(shù)由構(gòu)造函數(shù)中的 poolSize 來指定。用戶通過調(diào)用 execute() 方法來提交 Runnable 任務(wù),execute() 方法的內(nèi)部實現(xiàn)僅僅是將任務(wù)加入到 workQueue 中。線程池 內(nèi)部維護(hù)的工作線程會消費 workQueue 中的任務(wù)并執(zhí)行任務(wù),里面是一個 while 循環(huán)。

2.5 如何關(guān)閉線程池:

2.5.1 shutDown

shutdown調(diào)用的是advanceRunState(SHUTDOWN),而shutdownNow調(diào)用的是(STOP),即調(diào)用后設(shè)置的線程池狀態(tài)不同

2.5.2 shutDownNow

shutdown調(diào)用的是中斷空閑的Workers,而shutdownNow調(diào)用的是中斷所有的Workers shutdownNow會把所有任務(wù)隊列中的任務(wù)取出來,返回一個任務(wù)列表。而shutdown什么都不返回。

2.6 如何合理的配置線程池

要想合理地配置線程池,就必須首先分析任務(wù)特性要想合理地配置線程池,就必須首先分析任務(wù)特性任務(wù)的性質(zhì):CPU密集型任務(wù)、IO密集型任務(wù)和混合型任務(wù)。

任務(wù)的優(yōu)先級:高、中和低。

任務(wù)的執(zhí)行時間:長、中和短。

任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。

2.6.1 確定核心線程數(shù)

混合型的任務(wù),如果可以分成一個CPU密集型任務(wù)和一個IO密集型任務(wù),只要這兩個任務(wù)執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐量將高于串行執(zhí)行的吞吐量。如果這兩個任務(wù)執(zhí)行時間相差太大,則沒必要進(jìn)行分解。可以通過Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個數(shù)。

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

//核心線程數(shù)量大小

private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));

2.6.2 確定線程池最大容納線程數(shù)

由于IO密集型任務(wù)線程并不是一直在執(zhí)行任務(wù),則應(yīng)配置盡可能多的線程,如2*Ncpu。

private static final int maximumPoolSize = CPU_COUNT * 2 + 1;

2.6.3 線程池的創(chuàng)建

private static ThreadPoolProxy mBackgroundPool = null;

private static final Object mBackgroundLock = new Object();

private static ThreadPoolProxy mDownloadPool = null;

private static final Object mDownloadLock = new Object();

private static Map mMap = new HashMap<>();

private static final Object mSingleLock = new Object();

2.6.3.0 線程池策閱ThreadPoolProxy

public static class ThreadPoolProxy {

private ThreadPoolExecutor mPool;

private int mCorePoolSize;

private int mMaximumPoolSize;

private long mKeepAliveTime;

private boolean mIsPriority;

/**

* @param corePoolSize 核心線程數(shù)量

* @param maximumPoolSize 最大線程數(shù)量

* @param keepAliveTime 空閑線程存活時間,秒

*/

private ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime, boolean isPriority) {

mCorePoolSize = corePoolSize;

mMaximumPoolSize = maximumPoolSize;

mKeepAliveTime = keepAliveTime;

mIsPriority = isPriority;

}

執(zhí)行任務(wù),當(dāng)線程池處于關(guān)閉,將會重新創(chuàng)建新的線程池

public synchronized void execute(Runnable run) {

if (run == null) {

return;

}

if (mPool == null || mPool.isShutdown()) {

//ThreadFactory是每次創(chuàng)建新的線程工廠

if (mIsPriority) {//使用優(yōu)先級隊列

mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.SECONDS, new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new AbortPolicy());

} else {//隊列任務(wù)

mPool = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new AbortPolicy());

}

}

mPool.execute(run);

}

取消線程池中某個還未執(zhí)行的任務(wù)

public synchronized void remove(Runnable run) {

if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {

mPool.getQueue().remove(run);

}

}

/**

* 是否包含某個任務(wù)

*/

public synchronized boolean contains(Runnable run) {

if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {

return mPool.getQueue().contains(run);

} else {

return false;

}

}

關(guān)閉線程池

/**

* 關(guān)閉線程池,

*

* @param isNow if true 立即終止線程池,并嘗試打斷正在執(zhí)行的任務(wù),清空任務(wù)緩存隊列,返回尚未執(zhí)行的任務(wù)。

* if false ,確保所有已經(jīng)加入的任務(wù)都將會被執(zhí)行完畢才關(guān)閉,后面不接受任務(wù)

**/

public synchronized void shutdown(boolean isNow) {

if (mPool != null && (!mPool.isShutdown() || mPool.isTerminating())) {

if (isNow) {

mPool.shutdownNow();

} else {

mPool.shutdown();

}

}

}

}

}

2.6.3.1 CPU密集型獲取后臺線程池,核心線程會一直存活。

CPU密集型任務(wù)應(yīng)配置盡可能小的線程,如配置Ncpu+1個線程的線程池。

public static ThreadPoolProxy getBackgroundPool() {

synchronized (mBackgroundLock) {

if (mBackgroundPool == null) {

mBackgroundPool = new ThreadPoolProxy(corePoolSize, maximumPoolSize, 60L, false);

}

return mBackgroundPool;

}

}

2.6.3.2 并發(fā)線程池

獲取一個用于文件并發(fā)下載的線程池,修改核心線程數(shù)和最大線程數(shù)

public static ThreadPoolProxy getDownloadPool() {

synchronized (mDownloadLock) {

if (mDownloadPool == null) {

mDownloadPool = new ThreadPoolProxy(4, 12, 60L, true);

}

return mDownloadPool;

}

}

2.6.3.3 單線程池

獲取一個單線程池,所有任務(wù)將會被按照加入的順序執(zhí)行,免除了同步開銷的問題

public static ThreadPoolProxy getSinglePool(String name) {

synchronized (mSingleLock) {

ThreadPoolProxy singlePool = mMap.get(name);

if (singlePool == null) {

singlePool = new ThreadPoolProxy(0, 1, 60L, false);

mMap.put(name, singlePool);

}

return singlePool;

}

}

三. 簡易版下載器實現(xiàn)

3.0.1 定義當(dāng)前下載任務(wù)枚舉狀態(tài) DownloadStatus枚舉名意義

3.0.2 url,斷點下載開關(guān),線程優(yōu)先級,連接、讀取超時時間動態(tài)配置

public class DownloadConfig {

public String url;//下載地址,唯一標(biāo)示

public String targetPath;//下載成功后的文件路徑

public int intervalTime = 1000;//默認(rèn)每秒回調(diào)一次

public boolean isCallBackInUIThread = true;//默認(rèn)回調(diào)在主線程

public boolean isRange = true;//是否開啟斷點下載的功能,默認(rèn)開啟

public int timeOutMillisecond = 30 * 1000;//連接、讀取超時時間

public int priority;//默認(rèn)線程優(yōu)先級0,普通任務(wù)0,so庫下載優(yōu)先級8,其他更加情況自定義

public DownloadConfig(String url, String targetPath, boolean isRange, boolean isCallBackInUIThread, int intervalTime, int timeOutMillisecond) {

this.url = url;

this.targetPath = targetPath;

this.isRange = isRange;

this.isCallBackInUIThread = isCallBackInUIThread;

this.intervalTime = intervalTime;

this.timeOutMillisecond = timeOutMillisecond;

}

3.0.3 定制化下載任務(wù)接口 BaseDownloadTask方法名作用類型

3.0.4 下載器執(zhí)行文件下載接口化

public interface IDownloader{

void downloadFile(DownloadTask task);

}

3.0.5 配置下載監(jiān)聽器,提供監(jiān)聽成功,失敗,暫停,下載進(jìn)度回調(diào) DownloadListener方法名作用參數(shù)介紹

3.0.6 封裝單個下載任務(wù)

單任務(wù)Task實現(xiàn)了我們的 Runnable ,里面主要是執(zhí)行我們的任務(wù),Comparable 給我們的任務(wù)設(shè)定優(yōu)先級,BaseDownloadTask 就是給任務(wù)做一些簡單的任務(wù)開始,結(jié)束,暫停,取消下載等工作了

3.0.6.1 DownloadTask 成員變量

DownloadTask 成員變量需要關(guān)注幾個點:字段名作用類型

3.0.6.2 DownloadTask 構(gòu)造方法

DownloadTask 構(gòu)造方法有兩個,一個是將外部自定義所有參數(shù)塞到DownloadConfig,一個是直接將參數(shù)傳到DownloadTask里面

public DownloadTask(DownloadConfig config) {

this(config.url, config.targetPath, config.isRange, config.isCallBackInUIThread, config.intervalTime, config.timeOutMillisecond, config.priority);

}

public DownloadTask(String url, String targetPath, boolean isRange, boolean isCallBackInUIThread, int intervalTime, int timeOutMillisecond, int priority) {

this.url = url;

this.targetPath = targetPath;

this.isRange = isRange;

this.isCallBackInUIThread = isCallBackInUIThread;

this.intervalTime = intervalTime;

this.timeOutMillisecond = timeOutMillisecond;

if (this.intervalTime <= 0) {

this.intervalTime = 1000;

}

if (this.timeOutMillisecond <= 0) {

this.timeOutMillisecond = 30 * 1000;

}

this.status = DownloadStatus.STATUS_NONE;

this.tempFilePath = this.targetPath + tempSuffex;

this.priority = priority;

}

3.0.6.3 DownloadTask 添加任務(wù)add()

當(dāng)添加任務(wù)時候,我們給下載任務(wù)狀態(tài)置為等待,然后在這個方法里面初始化下載成功后的文件路徑以及對應(yīng)的文件名

@Override

public DownloadTask add() {

status = DownloadStatus.STATUS_WAITING;

final File file = new File(targetPath);

fileName = file.getName();

return this;

}

3.0.6.4 DownloadTask run() => 任務(wù)開始start()

如果下載緩存路徑合法,將該當(dāng)前本地已下載的size返回業(yè)務(wù)層

@Override

public long getDownloadedSize() {

if (!TextUtils.isEmpty(tempFilePath)) {

File file = new File(tempFilePath);

downloadedSize = file.exists() ? file.length() : 0;

} else {

downloadedSize = 0;

}

return downloadedSize;

}

3.0.6.5 設(shè)置Range斷點下載開關(guān)

如果配置了開啟斷點下載,且已經(jīng)下載過,設(shè)置Range.否則關(guān)閉斷點下載

public boolean isRangeRequest() {

return isRange && getDownloadedSize() > 0;

}

3.0.6.6 compareTo 比較任務(wù)的優(yōu)先級

@Override

public int compareTo(DownloadTask o) {//任務(wù)執(zhí)行優(yōu)先級排序

if (this.getPriority() < o.priority) {

return 1;

}

if (this.getPriority() > o.priority) {

return -1;

}

return 0;

}

3.0.6.7 DownloadTask run() => 開始下載任務(wù)start()

執(zhí)行任務(wù)開始的時候,重置任務(wù)狀態(tài),然后在run()方法里面調(diào)用下載器FileDownloader執(zhí)行任務(wù)下載

@Override

public void start() {

//狀態(tài)重置

this.status = DownloadStatus.STATUS_NONE;

//開始下載

if (downloader == null) {

downloader = new FileDownloader();

}

downloader.downloadFile(this);

}

3.0.6.8 DownloadTask 暫停下載任務(wù) pause()

在暫停下載任務(wù)方法里,將下載任務(wù)設(shè)置為暫停狀態(tài),然后用下載監(jiān)聽器將本地問價大小和總文件大小回傳給業(yè)務(wù)層

@Override

public void pause() {

status = DownloadStatus.STATUS_PAUSED;

if (listener != null) {

listener.onPaused(this, soFarBytes, totalSize);

}

}

3.0.6.9 DownloadTask 取消下載任務(wù) pause()

取消下載任務(wù)非常簡單,將下載任務(wù)設(shè)置為取消,然后刪除所有的副本文件

@Override

public void cancel() {

status = DownloadStatus.STATUS_NONE;

//刪除臨時文件

FileUtils.deleteFile(tempFilePath);

}

3.0.7.0 DownloadTask 設(shè)置DownloadListener下載監(jiān)聽器 setListener()

@Override

public DownloadTask setListener(DownloadListener listener) {

this.listener = listener;

return this;

}

還有一些常用的方法就不一一貼代碼了,詳細(xì)API文檔如下:方法名作用類型

3.0.7 封裝文件下載器FileDownloader,支持?jǐn)帱c下載

3.0.7.1 FileDownloader文件下載器 處理單一任務(wù)文件下載 downloadFile

downloadFile 整體看起來是比較簡單的,主線程的Looper是在這里初始化的,在這里我們默認(rèn)不走下載成功回調(diào),接著我們看看downloadByHttpURLConn是如何實現(xiàn)的

@Override

public void downloadFile(DownloadTask task) {

if (task == null) return;

if (handler == null) {

handler = new Handler(Looper.getMainLooper());

}

hasCallbackSuccess = false;

downloadByHttpURLConn(task);

}

3.0.7.2 downloadByHttpURLConn

downloadByHttpURLConn是 使用系統(tǒng)API下載,如果服務(wù)端支持206分段請求,可以斷點下載

在這里我們將下載狀態(tài)置為 STATUS_DOWNLOADING ,并且創(chuàng)建 HttpURLConnection 建立 Http 連接,下載我們默認(rèn)使用的是 GET 請求,并且整個連接處于Keep-Alive 長連接狀態(tài),不設(shè)置緩存,可配置化斷點下載入口

task.status = DownloadTask.DownloadStatus.STATUS_DOWNLOADING;

HttpURLConnection conn = null;

InputStream inputStream = null;

try {

URL url = new URL(task.url);

conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");

conn.setConnectTimeout(task.timeOutMillisecond);

conn.setReadTimeout(task.timeOutMillisecond);

conn.setRequestProperty("Connection", "Keep-Alive");

conn.setRequestProperty("Cache-Control", "no-cache");

conn.setRequestProperty("pragma", "no-cache");

conn.setRequestProperty("Accept", "*/*");

//是否開啟range請求,code=416,表示請求范圍不對

String rangStr = "";

if (task.isRangeRequest()) {//isRangeRequest()方法里面會獲取最新的本地緩存文件downloadedSize大小

rangStr = "bytes=" + task.downloadedSize + "-";

conn.setRequestProperty("Range", rangStr);//+ task.totalSize

//Logger.d(TAG, "isRangeRequest" + task.downloadedSize);

}獲取服務(wù)器返回的狀態(tài)碼

//獲取服務(wù)器返回的狀態(tài)碼

int code = conn.getResponseCode();

if (200 == code || 206 == code) { //200 請求服務(wù)器資源全部返回成功 //206 請求部分服務(wù)器資源返回成功

final String length = conn.getHeaderField("Content-Length");

if (!TextUtils.isEmpty(length)) {//總長度

task.totalSize = Long.parseLong(length);

}

if (task.totalSize == 0) {

final String transferEncoding = conn.getHeaderField("Transfer-Encoding");

task.isChunk = (transferEncoding != null && transferEncoding.equals("chunked"));

task.totalSize = TOTAL_VALUE_IN_CHUNKED_RESOURCE;

}

Map> map = conn.getHeaderFields();

//Logger.d(TAG, "code=" + code + ",length=" + length);

inputStream = conn.getInputStream();

startWriteFile(task, code, inputStream, rangStr, map);

} else {//出錯

task.errorCode = code;

if (416 == code) {//請求分段range有誤,本地臨時文件大小已經(jīng)是有問題的

//刪除臨時文件

FileUtils.deleteFile(task.tempFilePath);

}

handleError(task, new Throwable("net request error code=" + code + "|" + rangStr + "|url:" + task.url + "|tempFile:" + task.targetPath));

}200 請求服務(wù)器資源全部返回成功

206 請求部分服務(wù)器資源返回成功Content-Length 文件總大小等于0,我們?nèi)∠?dāng)前文件下載任務(wù)

非正常碼416請求分段range有誤,本地臨時文件大小已經(jīng)是有問題的,我們要刪除臨時文件

不要在出錯的時候暴力刪除本地緩存

private void handleError(final DownloadTask downloadTask, final Throwable e) {

if (downloadTask == null) return;

downloadTask.status = DownloadTask.DownloadStatus.STATUS_ERROR;

DownloadManager.init().removeTask(downloadTask.url);

if (downloadTask.isCallBackInUIThread) {

handler.post(() -> {

Logger.d(TAG, "download error " + e + ",isCallBackInUIThread=" + downloadTask.isCallBackInUIThread);

if (downloadTask.listener != null) {

downloadTask.listener.onError(downloadTask, e);

}

});

} else {

if (downloadTask.listener != null) {

downloadTask.listener.onError(downloadTask, e);

}

}

}

當(dāng)然我們不要在最好忘記關(guān)流和取消http連接

finally {

if (conn != null) conn.disconnect();

if (inputStream != null) {

try {

inputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

3.0.7.3 startWriteFile 寫文件

在downloadByHttpURLConn 成功返回碼有一個寫文件的方法startWriteFile,如果服務(wù)器支持206分段請求,寫文件用RandomAccessFile

寫文件的邏輯比較簡單,首先得刪除目標(biāo)文件,然后在構(gòu)建一個副本文件如果是200所以不用做斷點續(xù)傳處理

if (200 == code) {//非range請求

tempFile.delete();

downloadTask.downloadedSize = 0;

fileOutputStream = new FileOutputStream(tempFile);

}如果是206,斷點續(xù)傳

(206 == code) {//斷點續(xù)傳

currentSize = tempFile.length();

downloadTask.downloadedSize = currentSize;

raf = new RandomAccessFile(tempPath, "rw");

raf.seek(currentSize);

Logger.d(TAG, "206 range downloadedSize=" + currentSize + ",totalSize=" + downloadTask.totalSize);

}

然后執(zhí)行文件讀寫工作,中間有三個注意事項

while ((len = inputStream.read(buffer)) != -1 && DownloadTask.DownloadStatus.STATUS_DOWNLOADING == downloadTask.status) {

if (200 == code) {

fileOutputStream.write(buffer, 0, len);

fileOutputStream.flush();

} else if (206 == code) {

raf.write(buffer, 0, len);

}

}每intervalTime毫秒回調(diào)一次,完成了下載必須回調(diào)

soFarBytes += len;

downloadTask.downloadedSize = currentSize + soFarBytes;

downloadTask.soFarBytes = soFarBytes;

if (System.currentTimeMillis() - start >= downloadTask.intervalTime || soFarBytes == downloadTask.totalSize) {

notifyProgress(downloadTask, soFarBytes);

start = System.currentTimeMillis();

}

}本地文件大小和服務(wù)器文件大小一樣,說明文件下載完畢執(zhí)行下載成功回調(diào)

if (downloadTask.soFarBytes == downloadTask.totalSize) {

handleSuccess(downloadTask);

}寫文件出錯了,停止下載,回調(diào)錯誤.如果是塊傳輸,totalSize不是實際總大小

if (downloadTask.totalSize > 0 && downloadTask.soFarBytes > downloadTask.totalSize && !downloadTask.isChunk) {

String errorMsg = ERROR_DOWNLOAD_MORE_SIZE + " localSize=" + downloadTask.downloadedSize +

",soFar=" + downloadTask.soFarBytes + ",total=" + downloadTask.totalSize;

Logger.d(TAG, errorMsg);

downloadTask.cancel();

handleError(downloadTask, new RuntimeException(errorMsg));

}如果STATUS_DOWNLOADING下載狀態(tài)的文件,沒有回調(diào)成功,則回調(diào)失敗

if (DownloadTask.DownloadStatus.STATUS_DOWNLOADING == downloadTask.status) {

if (!hasCallbackSuccess &&

(!downloadTask.isChunk && downloadTask.soFarBytes == downloadTask.totalSize)) {

//非Chunk下載,并且下載的大小和總大小一致回調(diào)成功

handleSuccess(downloadTask);

} else if (!hasCallbackSuccess && downloadTask.isChunk) {

//Chunk下載,暫時可以認(rèn)為成功

handleSuccess(downloadTask);

} else {

//失敗

StringBuilder headStr = new StringBuilder();

if (null != map) {

for (String key : map.keySet()) {

headStr.append("|").append(key).append("=").append(map.get(key).toString());

}

}

handleError(downloadTask, new Throwable("Error WriteFile,hasCallSuc:" + hasCallbackSuccess + "|headerStr:" + headStr + "|bytes:" + downloadTask.soFarBytes + "-" + downloadTask.totalSize + "-" + downloadTask.downloadedSize +

"|" + rangStr + "|url:" + downloadTask.url + "|tempFile:" + tempFile.getPath() + "-" + tempFile.length()));

}

}

3.0.7.4 進(jìn)度通知回調(diào) notifyProgress

如果當(dāng)前任務(wù)在主線程中運行,那么我們就把下載進(jìn)度更新操作切換到UI線程

private void notifyProgress(final DownloadTask downloadTask, final long soFarBytes) {

if (DownloadTask.DownloadStatus.STATUS_DOWNLOADING == downloadTask.status) {

if (downloadTask.isCallBackInUIThread) {

handler.post(() -> {

if (downloadTask.listener != null) {

downloadTask.listener.onProgress(downloadTask.downloadedSize, soFarBytes, downloadTask.totalSize);

}

});

} else {

if (downloadTask.listener != null) {

downloadTask.listener.onProgress(downloadTask.downloadedSize, soFarBytes, downloadTask.totalSize);

}

}

}

}

3.0.7.5 下載成功回調(diào) handleSuccess

如果是分段傳輸,我們要保證一點下載文件大小要等于總文件大小 下載成功后,重命名,去除.temp臨時文件,并且將該鏈接下載任務(wù)從隊列移除,然后將成功下載回調(diào)給業(yè)務(wù)層

private void handleSuccess(final DownloadTask downloadTask) {

if (downloadTask == null) return;

if (hasCallbackSuccess) return;

downloadTask.status = DownloadTask.DownloadStatus.STATUS_DOWNLOADED;

if (downloadTask.isChunk) {

downloadTask.totalSize = downloadTask.downloadedSize;//保證最后完成下載時的文件大小回調(diào)

}

//完整下載后,重命名,去除.temp臨時文件

final File targetFile = new File(downloadTask.targetPath);

if (targetFile.exists()) {

targetFile.delete();

}

final File tempDownloadedFile = new File(downloadTask.tempFilePath);

tempDownloadedFile.renameTo(targetFile);

//Log.d("dq-handleSuccess", targetFile.getName() + ",url=" + downloadTask.url);

DownloadManager.init().removeTask(downloadTask.url);

if (downloadTask.isCallBackInUIThread) {

handler.post(() -> {

if (downloadTask.listener != null) {

downloadTask.listener.onSuccess(downloadTask);

}

});

} else {

if (downloadTask.listener != null) {

downloadTask.listener.onSuccess(downloadTask);

}

}

hasCallbackSuccess = true;

}

3.0.7.6 下載失敗回調(diào) handleSuccess

不要在出錯的時候暴力刪除本地緩存,這樣可能其他回調(diào)會受到影響

private void handleError(final DownloadTask downloadTask, final Throwable e) {

if (downloadTask == null) return;

downloadTask.status = DownloadTask.DownloadStatus.STATUS_ERROR;

DownloadManager.init().removeTask(downloadTask.url);

if (downloadTask.isCallBackInUIThread) {

handler.post(() -> {

Logger.d(TAG, "download error " + e + ",isCallBackInUIThread=" + downloadTask.isCallBackInUIThread);

if (downloadTask.listener != null) {

downloadTask.listener.onError(downloadTask, e);

}

});

} else {

if (downloadTask.listener != null) {

downloadTask.listener.onError(downloadTask, e);

}

}

}

3.0.8 下載管理器 DownloadManager 封裝

3.0.8.1 DownloadManager()

DownloadManager 構(gòu)造器主要拿到主線程的Looper, 以及初始化普通的下載線程池

private DownloadManager() {

mTaskMap = new ConcurrentHashMap<>();

mHandler = new Handler(Looper.getMainLooper());

downloadPool = ThreadManager.getDownloadPool();//普通的下載線程池;

}

3.0.8.2 init()

DownloadManager 是一個最簡單的單例實現(xiàn),我們獲取DownloadManager 引用直接調(diào)用init 即可

public static DownloadManager init() {

return SingletonHolder.INSTANCE;

}

3.0.8.3 download() 文件下載

初始化文件下載任務(wù),加入下載的線程池,默認(rèn)在子線程回調(diào)的下載任務(wù)

/**

* 方便外部自定義所有參數(shù)

*

* @param config 下載參數(shù),可擴(kuò)展

* @param listener 下載監(jiān)聽,回調(diào)

* @return 返回當(dāng)前下載任務(wù)

*/

public synchronized DownloadTask download(DownloadConfig config, DownloadListener listener) {

if (config == null || TextUtils.isEmpty(config.url) || TextUtils.isEmpty(config.targetPath)) {

throw new NullPointerException();

}

final DownloadTask downloadTask = new DownloadTask(config)

.setListener(listener)

.add();

addToDownloader(downloadTask);

return downloadTask;

}

public synchronized DownloadTask download(String url, String targetPath, boolean isRange, boolean isCallBackInUIThread, int intervalTime, int timeOutMillisecond, DownloadListener listener) {

if (TextUtils.isEmpty(url) || TextUtils.isEmpty(targetPath)) {

throw new NullPointerException();

}

final DownloadTask downloadTask = new DownloadTask(url, targetPath, isRange, isCallBackInUIThread, intervalTime, timeOutMillisecond, 0)

.setListener(listener)

.add();

addToDownloader(downloadTask);

return downloadTask;

}

3.0.8.4 添加下載任務(wù)

如果下載的文件有版本差異,下載前請清除緩存文件,防止出現(xiàn)不同時段寫入文件,導(dǎo)致錯誤

public synchronized void addToDownloader(DownloadTask task) {

if (task == null || TextUtils.isEmpty(task.url)) {

throw new NullPointerException();

}

DownloadTask downloadTask = mTaskMap.get(task.url);

if (downloadTask == null) { // 創(chuàng)建一個新的下載任務(wù),放入線程池

mTaskMap.put(task.url, task);

downloadPool.execute(task);

//Logger.d(TAG, "dq-start download:" + task.getUrl());

} else {

downloadTask.pause();

downloadTask.setListener(task.listener);//重新設(shè)置監(jiān)聽

downloadPool.remove(downloadTask);

downloadPool.execute(downloadTask);//直接執(zhí)行

}

}

3.0.8.5 暫停下載 pause

public synchronized boolean pause(String url) {

if (!TextUtils.isEmpty(url)) {

DownloadTask downloadTask = mTaskMap.get(url);

if (downloadTask != null) {

downloadTask.setListener(null);

downloadTask.pause();

removeTask(url);

//Logger.d(TAG, "pause download:" + downloadTask);

return true;

}

}

return false;

}

3.0.8.6 取消下載 cancel

取消下載,將監(jiān)聽器置為null,然后清除已經(jīng)下載文件,最后在線程池池里面remove task 即可

public synchronized void cancel(String url) {

if (TextUtils.isEmpty(url)) {

throw new NullPointerException();

}

DownloadTask downloadTask = mTaskMap.get(url);

if (downloadTask != null) {

downloadTask.setListener(null);

downloadTask.cancel();//會清除已經(jīng)下載的文件

removeTask(url);

//Logger.d(TAG, "cancel download:" + downloadTask);

}

}

3.0.8.7 清除task緩存 removeTask

清除任務(wù)比較簡單,直接在線程池里面remove task 即可

public synchronized DownloadTask removeTask(String url) {

final DownloadTask downloadTask = mTaskMap.remove(url);

if (downloadTask != null) {

downloadPool.remove(downloadTask);

}

return downloadTask;

}

3.0.8.8 程序退出時停止下載任務(wù) onAppExit

退出應(yīng)用程序前務(wù)必清除所有任務(wù)隊列暫停然后清除里面所有的任務(wù)

public synchronized void onAppExit() {

for (DownloadTask downloadTask : mTaskMap.values()) {

downloadTask.setListener(null);

downloadTask.pause();

downloadPool.remove(downloadTask);

}

mTaskMap.clear();

downloadPool.shutdown(true);

}

總結(jié)

以上是生活随笔為你收集整理的renameto 阻塞_打造简化版文件下载器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。