Android Download機制詳解(一)DocumentUI部分


在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的入口。它的入口一般有兩個:

  1. Launcher上的“下載”app
  2. 被其他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的啟動流程分兩步來分析

  1. 初始化狀態信息
  2. 查詢和顯示對應的數據

初始化狀態信息的流程如下:

在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主要經歷一下一個步驟:

  1. 從DownloadList跳轉到DocumentsActivity
  2. 保存需要顯示的內容信息
  3. 通過DirectoryLoader完成異步查詢數據
  4. 顯示數據

 Android L 源代碼在線查看http://androidxref.com/5.1.0_r1/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM