關于Android自帶的DownloadManager可能大家都知道,使用自帶的DownloadManager可以很方便的就實現文件下載。還不知道的也沒關系,看看下面的示例代碼你就知道怎么去使用了。
DownloadManager manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
//下載請求
DownloadManager.Request down = new DownloadManager.Request(Uri.parse("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png"));
//設置允許使用的網絡類型
down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
//禁止發出通知,既后臺下載
down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
//下載完成文件存放的位置
down.setDestinationInExternalFilesDir(MainActivity.this, null, "logo.png");
//將下載請求放入隊列中,開始下載
manager.enqueue(down);
是不是很簡單,直接使用系統自帶的,幾行代碼就搞定了一個下載。使用這種方法,默認的會在系統的通知欄告訴用戶有個下載的任務正在進行,如果不想讓用戶知道有任務正在進行你就必須加上
down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);````
但是單單加這個還是不行的,你不信可以運行一次試試,應用會直接Crash掉。我們還需要在清單文件聲明下面這個權限:
```html
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
如果不再在意這些直接使用系統自帶的DownloadManager 就足夠了,但是很多人不想聲明權限,就想讓任務在后臺運行(ps:不要問我為什么,我什么都不知道),這樣就需要自己去實現下載功能了。
該說說ThinDownloadManager了,其實我也是無意中發現這個的,因為我想在我的開源項目MaterialDesignDemo中實現一個圖片下載的功能,但是又不想自己去造輪子,我就想有一個下載功能而已,也不想使用那些很笨重的網絡框架,所以就去到處找,最后發現了這個東東,比較輕量級,使用起來也還是比較的方便。因為我自己之前本來也計劃自己去擼一個網絡相關的框架,但是如果想寫一個網絡框架涉及的東西太多了,而我現在也還沒有達到那個層次,所以就放棄了,平時使用的也就是對一些開源框架的再封裝而已。
現在發現了這個,也還是對他的源碼比較好奇,所以就決定去研究一下他的源碼,看完了之后,我發現這個框架的結構比較簡單不算復雜,于是就決定把我怎么去分析ThinDownloadManager這個框架源碼的過程記錄下來,給那些還不知道怎么去分析源碼的同學一點經驗。雖然這個庫在現在已經有點過時了,但是他的一些思想還是值得我們去學習的。學習Android一定要學會看源碼,不要一遇到問題就一昧的去網上搜,這個怎么實現,那個怎么實現,其實你只要先看看源碼,可能問題很容易的就解決了,看源碼對自己的提升也是顯而易見的,就算以后Android不火了,但是他的很多編程思想是值得借鑒的
ThinDownloadManager的GitHub地址:ThinDownloadManager
在分析源碼之前我們先來看看ThinDownloadManager怎么去使用吧,如果連怎么使用都不會,分析源碼也就是紙上談兵了
ThinDownloadManager的使用也還是比較簡單的,和系統自帶的差不多,我這里是為了我以后使用的方便所以就做了一次再封裝,只需傳入下載地址,保存地址,還有下載時的回調就可以了。
- 根據使用的順序,我們就先來看看DownloadRequest,其實根據單詞意思我們都能大概知道這里面是封裝的肯定是下載請求的一些參數。
先來看看他的構造方法
public DownloadRequest(Uri uri) {
if (uri == null) {
throw new NullPointerException();
}
String scheme = uri.getScheme();
if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
}
mCustomHeader = new HashMap<>();
mDownloadState = DownloadManager.STATUS_PENDING;
mUri = uri;
}
在構造方法中對傳入的uri做了校驗,以及一些初始化的操作。
在示例代碼中我們在使用DownloadRequest的時候,我們用到了setRetryPolicy和setDestinationURI以及setStatusListener,所以現在我們定位到這幾個方法的源碼中去。
看看setRetryPolicy(關于RetryPolicy 就是重試策略而已,在第一次看的時候,需先走一遍的流程,不需要去在意部分細節)
public DownloadRequest setRetryPolicy(RetryPolicy mRetryPolicy) {
this.mRetryPolicy = mRetryPolicy;
return this;
}
這里的實現就是一個簡單的賦值操作而已,既然有賦值方法,那么應該就有對應的取值方法,我Ctrl+F找了一下果然有getRetryPolicy這么一個方法。
public RetryPolicy getRetryPolicy() {
return mRetryPolicy == null ? new DefaultRetryPolicy() : mRetryPolicy;
}
從這里看出如果mRetryPolicy 設置了,我們就返回mRetryPolicy ,如果沒有被設置我們就返回默認的。從這里可見在示例代碼中的那句<code>setRetryPolicy(new DefaultRetryPolicy())</code>
是可以不用設置的,因為默認的就是DefaultRetryPolicy。
再來看看setDestinationURI這個方法
public DownloadRequest setDestinationURI(Uri destinationURI) {
this.mDestinationURI = destinationURI;
return this;
}
和setRetryPolicy方法一樣,就是一個簡單的賦值操作而已。
setStatusListener同理,我就不再做多余的解釋了。
- 當DownloadRequest配置完成之后,我們就使用到了ThinDownloadManager,也還是先進他的構造方法里面去看看。ThinDownloadManager的構造方法就不只一個了,我們就直接來看看他默認的吧
public ThinDownloadManager() {
mRequestQueue = new DownloadRequestQueue();
mRequestQueue.start();
}
在這里他新建了一個下載的請求隊列DownloadRequestQueue,然后調用了DownloadRequestQueue的start方法。
再來看看ThinDownloadManager的add方法都干了些什么
public int add(DownloadRequest request) throws IllegalArgumentException {
if(request == null) {
throw new IllegalArgumentException("DownloadRequest cannot be null");
}
return mRequestQueue.add(request);
}
從源碼中可以看到add方法就是把我們的下載請求添加到DownloadRequestQueue這個請求隊列中去。
- 在分析ThinDownloadManager這個類的時候,我們發現DownloadRequestQueue用到的比較多,并且也比較重要,現在我們就定位到DownloadRequestQueue這個類中去。一樣我們還是先來看看其構造方法,一般在使用一個類的時候,我們都需要先初始化他,所以先看構造方法也還是有必要的。
/**
* Default constructor.
*/
public DownloadRequestQueue() {
initialize(new Handler(Looper.getMainLooper()));
}
在這里他調用了 initialize方法,所以我們就來看看initialize的實現
private void initialize(Handler callbackHandler) {
int processors = Runtime.getRuntime().availableProcessors();
mDownloadDispatchers = new DownloadDispatcher[processors];
mDelivery = new CallBackDelivery(callbackHandler);
}
在這里processors 就是java虛擬機可用的處理器個數,最初的時候我也不知道這是啥,最后去查了一下才知道,所以看源碼還是能學到很多東西的,廢話不多說,我們繼續。從代碼中可以看出在initialize中新建了一個processors 大小的DownloadDispatcher數組,以及CallBackDelivery。
在ThinDownloadManager的構造方法中調用了DownloadRequestQueue的start方法,所以這里我們就先來看看start方法
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create download dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDownloadDispatchers.length; i++) {
DownloadDispatcher downloadDispatcher = new DownloadDispatcher(mDownloadQueue, mDelivery);
mDownloadDispatchers[i] = downloadDispatcher;
downloadDispatcher.start();
}
}
stop可以先不用去看他,直接來看for循環里面都干了些什么,可以看到在for循環里就是對mDownloadDispatchers中的每一個DownloadDispatcher 做了一次初始化操作
隨后就調用了DownloadDispatcher 的start方法。
4.上面這么多的代碼都涉及到了DownloadDispatcher ,下面我們就來看看DownloadDispatcher 的實現吧。
其構造方法如下:
/** Constructor take the dependency (DownloadRequest queue) that all the Dispatcher needs */
public DownloadDispatcher(BlockingQueue<DownloadRequest> queue,
DownloadRequestQueue.CallBackDelivery delivery) {
mQueue = queue;
mDelivery = delivery;
}
在這里就傳了一個下載請求的隊列和CallBackDelivery(CallBackDelivery是用來進行回調的分發用的,是DownloadRequestQueue的一個內部類,后面自己去看源碼就知道了,第一次走流程的時候不需要太在意這個) 進來并給成員變量賦值。在DownloadRequestQueue使用到了downloadDispatcher.start(),所以我就點進start去看看,結果發現怎么進了Thread的start方法了,仔細看了一下DownloadDispatcher 源碼,發現原來DownloadDispatcher 繼承了Thread,也就是說DownloadDispatcher 其實就是一個線程,所以之前調用了start方法就是讓這個線程開始執行,既然繼承了Thread那么肯定得有run方法吧,于是我就定位到了run方法:
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mTimer = new Timer();
while(true) {
try {
mRequest = mQueue.take();
mRedirectionCount = 0;
Log.v(TAG, "Download initiated for " + mRequest.getDownloadId());
updateDownloadState(DownloadManager.STATUS_STARTED);
executeDownload(mRequest.getUri().toString());
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
if(mRequest != null) {
mRequest.finish();
updateDownloadFailed(DownloadManager.ERROR_DOWNLOAD_CANCELLED, "Download cancelled");
mTimer.cancel();
}
return;
}
continue;
}
}
}
可見在run方法中有一個while的無限循環,里面的最主要的事的就是從當前的請求隊列中取出一個請求然后調用executeDownload去執行下載。(PS:因為這個隊列是可阻塞的隊列,所以當隊列里沒有任何請求的話,就會阻塞在mQueue.take(),直到隊列中有請求的時候,才會繼續向下執行,這個是Java并發編程相關的知識點)
private void executeDownload(String downloadUrl) {
URL url;
try {
url = new URL(downloadUrl);
} catch (MalformedURLException e) {
updateDownloadFailed(DownloadManager.ERROR_MALFORMED_URI,"MalformedURLException: URI passed is malformed.");
return;
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setConnectTimeout(mRequest.getRetryPolicy().getCurrentTimeout());
conn.setReadTimeout(mRequest.getRetryPolicy().getCurrentTimeout());
HashMap<String, String> customHeaders = mRequest.getCustomHeaders();
if (customHeaders != null) {
for (String headerName : customHeaders.keySet()) {
conn.addRequestProperty(headerName, customHeaders.get(headerName));
}
}
// Status Connecting is set here before
// urlConnection is trying to connect to destination.
updateDownloadState(DownloadManager.STATUS_CONNECTING);
final int responseCode = conn.getResponseCode();
Log.v(TAG, "Response code obtained for downloaded Id "
+ mRequest.getDownloadId()
+ " : httpResponse Code "
+ responseCode);
switch (responseCode) {
case HTTP_PARTIAL:
case HTTP_OK:
shouldAllowRedirects = false;
if (readResponseHeaders(conn) == 1) {
transferData(conn);
} else {
updateDownloadFailed(DownloadManager.ERROR_DOWNLOAD_SIZE_UNKNOWN, "Transfer-Encoding not found as well as can't know size of download, giving up");
}
return;
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
case HTTP_TEMP_REDIRECT:
// Take redirect url and call executeDownload recursively until
// MAX_REDIRECT is reached.
while (mRedirectionCount++ < MAX_REDIRECTS && shouldAllowRedirects) {
Log.v(TAG, "Redirect for downloaded Id "+mRequest.getDownloadId());
final String location = conn.getHeaderField("Location");
executeDownload(location);
continue;
}
if (mRedirectionCount > MAX_REDIRECTS) {
updateDownloadFailed(DownloadManager.ERROR_TOO_MANY_REDIRECTS, "Too many redirects, giving up");
return;
}
break;
case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
updateDownloadFailed(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE, conn.getResponseMessage());
break;
case HTTP_UNAVAILABLE:
updateDownloadFailed(HTTP_UNAVAILABLE, conn.getResponseMessage());
break;
case HTTP_INTERNAL_ERROR:
updateDownloadFailed(HTTP_INTERNAL_ERROR, conn.getResponseMessage());
break;
default:
updateDownloadFailed(DownloadManager.ERROR_UNHANDLED_HTTP_CODE, "Unhandled HTTP response:" + responseCode +" message:" +conn.getResponseMessage());
break;
}
} catch(SocketTimeoutException e) {
e.printStackTrace();
// Retry.
attemptRetryOnTimeOutException();
} catch (ConnectTimeoutException e) {
e.printStackTrace();
attemptRetryOnTimeOutException();
} catch(IOException e) {
e.printStackTrace();
updateDownloadFailed(DownloadManager.ERROR_HTTP_DATA_ERROR, "Trouble with low-level sockets");
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
學習過Android網絡編程的同學肯定對這里面的代碼很熟悉了,從代碼中可以看到其實ThinDownloadManager的底層就是用HttpURLConnection去實現的,其他的代碼其實就是去更新當前下載的狀態,還有網絡連接超時的重試等等。
到這里整個過程就分析的差不多了,更詳細的實現可以去下載ThinDownloadManager源碼自己詳看。
通過上面的分析大致流可以總結為:先創建一個下載請求DownloadRequest,隨后再新建一個請求隊列DownloadRequestQueue和下載器DownloadDispatcher,下載器DownloadDispatcher隨即開始執行,然后再將請求加入到下載請求隊列中由下載器去從請求隊列中取出下載請求,然后下載。
ThinDownloadManager的源碼的層次結構還是比較清晰的,所以看起來相對也比較容易,看源碼一般都是從我們使用的地方進去,一步一步的跟進,剛開始注重流程,不要太去部分在意細節的實現,流程走通了,然后再來看實現,我相信你會收獲的更多,本文主要介紹了看源碼的其中一種方法,僅供參考,可以讓你看源碼的時候多一種思路,希望能給正在閱讀的你帶來幫助。