在Android中Google為我們集成了一套十分便利的Download機制,用來下載網絡上的資源文件。以此省去了我們編寫和維護大量與Download相關的代碼。
組成
Android中Download由三個部分組成:
1.DocumentsUI -----> /frameworks/base/packages/DocumentsUI/
2.DownloadManager ---->/frameworks/base/core/java/android/app/
3.DownloadProvider ---->/packages/providers/DownloadProvider/
下圖中用MVC的分層將這三部分做了划分:
其中DocumentsUI作為視圖層(V)負責展示Download信息
DownloadManager和DownloadProvder的一部分作為控制層(C)負責下載的邏輯控制
DownloadProvder的另一部分則作為數據層(M)負責數據的存儲
總流程
Download的整個流程在上圖中已經表示的很明顯了,這里不做過對贅述,詳細講解將放在后面。
值得注意的是DownloadManager看似是主宰整個下載過程的角色,但事實並非如此正真的幕后“黑手”是DownloadProvider。
詳細分析
DocumentUI--數據顯示篇
DocumentsUI是一個可見程序,但即便如此Launcher上也沒有直接打開的DocumentsUI的入口。它的入口一般有兩個:
- Launcher上的“下載”app
- 被其他app喚起如(短信點擊添加附件后喚起的app就是DocumentsUI)
這里我們只分析1這種情況,情況2的話感興趣的同學可以自己學習一下。
“下載”這個app的代碼被包含在了DownloadProvider中,具體位置如下:
上圖中的ui文件夾就是包含“下載”app的所有代碼.
根據AndroidManifest文件判斷,點擊“下載”app首先啟動的activity是:
/packages/providers/DownloadProvider/ui/src/com/android/providers/downloads/ui/DownloadList.java
我們來看一下這個文件的內容:
17package com.android.providers.downloads.ui; 18 19import android.app.Activity; 20import android.content.Intent; 21import android.os.Bundle; 22import android.provider.DocumentsContract; 23 24import com.android.providers.downloads.Constants; 25 26public class DownloadList extends Activity { 27 @Override 28 public void onCreate(Bundle icicle) { 29 super.onCreate(icicle); 30 31 // Trampoline over to new management UI 32 final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT); 33 intent.setData(DocumentsContract.buildRootUri( 34 Constants.STORAGE_AUTHORITY, Constants.STORAGE_ROOT_ID)); 35 startActivity(intent); 36 finish(); 37 } 38}
看到這里大家應該知道了,其實這個“下載”app只是一個傳送門,傳送門的另一邊是Action包含“DocumentsContract.ACTION_MANAGE_ROOT”的Activity,那么這個神秘的Activity到底是何方神聖呢?
我想我不說大家也應該猜到了,這個Activity肯定是存在與DocumentsUI中,因為上面我們已經說到過了,DocumentUI是整個下載系統的視圖層。那么下面就轉戰我們這一小節的主角DocumentsUI。
通過DocumentsUI的AndroidManifest文件知道,接受“下載”發出的Intent的Activity就是上面的DocumentsActivity,它的路徑是
/frameworks/base/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
我將DocumentsActivity的啟動流程分兩步來分析
- 初始化狀態信息
- 查詢和顯示對應的數據
初始化狀態信息的流程如下:
在onCreate方法中會調用buildDefaultState方法來初始化mState對象。State是專門存儲狀態信息的
223 private void buildDefaultState() { 224 mState = new State(); 225 226 final Intent intent = getIntent(); 227 final String action = intent.getAction(); 228 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { 229 mState.action = ACTION_OPEN; 230 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { 231 mState.action = ACTION_CREATE; 232 } else if (Intent.ACTION_GET_CONTENT.equals(action)) { 233 mState.action = ACTION_GET_CONTENT; 234 } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) { 235 mState.action = ACTION_OPEN_TREE; 236 } else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) { 237 mState.action = ACTION_MANAGE; 238 } 239 240 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 241 mState.allowMultiple = intent.getBooleanExtra( 242 Intent.EXTRA_ALLOW_MULTIPLE, false); 243 } 244 245 if (mState.action == ACTION_MANAGE) { 246 mState.acceptMimes = new String[] { "*/*" }; 247 mState.allowMultiple = true; 248 } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { 249 mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); 250 } else { 251 mState.acceptMimes = new String[] { intent.getType() }; 252 } 253 254 mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); 255 mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false); 256 mState.showAdvanced = mState.forceAdvanced 257 | LocalPreferences.getDisplayAdvancedDevices(this); 258 259 if (mState.action == ACTION_MANAGE) { 260 mState.showSize = true; 261 } else { 262 mState.showSize = LocalPreferences.getDisplayFileSize(this); 263 } 264 }
由於前面傳入的action為“DocumentsContract.ACTION_MANAGE_ROOT”,這里會走236行將mState.action 設置為 ACTION_MANAGE
在Sate類中的restored變量初始值為false,所以在onCreate方法中會走下面這段代碼
211 if (!mState.restored) { 212 if (mState.action == ACTION_MANAGE) { 213 final Uri rootUri = getIntent().getData(); 214 new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor()); 215 } else { 216 new RestoreStackTask().execute(); 217 } 218 } else { 219 onCurrentDirectoryChanged(ANIM_NONE); 220 }
通過調用RestoreRootTask來將當面信息保存下來,接着調用onRootPicked來對需要顯示的內容做相應判斷。
在onRootPicked中又會啟動另一個異步任務PickRootTask,它主要作用是通過intent中的data信息來構建DocumentInfo的對象,該對象中主要保存一下字段
56 public String authority; 57 public String documentId; 58 public String mimeType; 59 public String displayName; 60 public long lastModified; 61 public int flags; 62 public String summary; 63 public long size; 64 public int icon;
這里的authority現在的值為"com.android.providers.downloads.documents",documentId的值為"downloads"
獲得了以上信息后,PickRootTask的任務差不多就完成了,接着它會調用onCurrentDirectoryChanged來告訴DocumentsUI,“有人要顯示所有下載的信息,我這邊的信息處理完了你可以去查詢和顯示下載信息了”
查詢和顯示對應的數據流程如下
首先在onCreateView方法中初始化界面布局,接着在onActivityCreated中初始化Adapter和異步數據加載器,最后將從數據庫取出來的數據與Adapter進行綁定。
這里我們重點看異步查詢數據部分
mCallbacks = new LoaderCallbacks<DirectoryResult>() { 262 @Override 263 public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { 264 final String query = getArguments().getString(EXTRA_QUERY); 265 266 Uri contentsUri; 267 switch (mType) { 268 case TYPE_NORMAL: 269 contentsUri = DocumentsContract.buildChildDocumentsUri( 270 doc.authority, doc.documentId); 271 if (state.action == ACTION_MANAGE) { 272 contentsUri = DocumentsContract.setManageMode(contentsUri); 273 } 274 return new DirectoryLoader( 275 context, mType, root, doc, contentsUri, state.userSortOrder); 276 //部分代碼省略
289 } 290 } 291 292 @Override 293 public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { 294 if (!isAdded()) return; 295 296 mAdapter.swapResult(result); 297 //部分代碼省略 321 322 mLastSortOrder = state.derivedSortOrder; 323 } 324 325 @Override 326 public void onLoaderReset(Loader<DirectoryResult> loader) { 327 mAdapter.swapResult(null); 328 } 329 };
在之前的講述中知道state.actio的值為ACTION_MANAGER,onCreateLoader會走271行代碼,然后返回一個DirectoryLoader來進行數據的查詢。當DirectoryLoader查詢完數據后系統會回調onLoadFinished方法,最后通過
mAdapter.swapResult(result);來將數據與Adapter綁定。Adapter有了數據的更新自然就會去更新界面,那么此時從打開“下載”app到整個界面解顯示就結束了。最終界面如下圖:
總結一下,點擊“下載”app主要經歷一下一個步驟:
- 從DownloadList跳轉到DocumentsActivity
- 保存需要顯示的內容信息
- 通過DirectoryLoader完成異步查詢數據
- 顯示數據