源碼學(xué)習(xí)|Android N DownloadManager源碼分析

DownloadManager是Android用系統(tǒng)服務(wù)的方式提供的用來(lái)優(yōu)化處理長(zhǎng)時(shí)間下載任務(wù)的工具巍实。
本文將基于Android N的源碼進(jìn)行分析。

DownloadManager的使用方式
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Uri uri = Uri.parse("downloadUrl");
DownloadManager.Request request = new Request(uri);
long reference = downloadManager.enqueue(request);

調(diào)用enqueue方法之后东涡,只要數(shù)據(jù)連接可用并且Download Manager可用冯吓,下載就會(huì)開(kāi)始。
要在下載完成的時(shí)候獲得一個(gè)系統(tǒng)通知(notification),注冊(cè)一個(gè)廣播接受者來(lái)接收ACTION_DOWNLOAD_COMPLETE廣播疮跑,這個(gè)廣播會(huì)包含一個(gè)EXTRA_DOWNLOAD_ID信息在intent中包含了已經(jīng)完成的這個(gè)下載的ID组贺。

其他更詳細(xì)API使用方法請(qǐng)參考Android DownloadManager的使用一文,此處不再詳述顷牌。

DownloadManager的調(diào)用處理

DownloadManager的執(zhí)行入口方法enqueue的源碼如下所示:

ContentValues values = request.toContentValues(mPackageName);
Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
long id = Long.parseLong(downloadUri.getLastPathSegment());
return id;

其中卫病,request為請(qǐng)求初始化傳入的DownloadManager.Rquest對(duì)象勿负,傳入請(qǐng)求后
toContentValues()方法會(huì)以傳入包名將待插入的數(shù)據(jù)生成ContentValues,方法中會(huì)有一個(gè)斷言檢查,代碼如下所示:

ContentValues toContentValues(String packageName) {
    ContentValues values = new ContentValues();
    assert mUri != null;
    //.......
}

其實(shí)看到這處斷言檢查有點(diǎn)疑惑峡继,在構(gòu)造Uri對(duì)象的時(shí)候已經(jīng)進(jìn)行了空判斷呻畸,為什么此處還要進(jìn)行一次斷言檢查呢,不是會(huì)有冗余嗎?

在插入ContentValues時(shí)写穴,mResolver.insert()實(shí)際調(diào)用的是系統(tǒng)DownloadProvider中的insert方法,插入返回的downloadUri會(huì)在原有Uri基礎(chǔ)上調(diào)用ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID)添加一個(gè)rowId返回一個(gè)形如content://downloads/my_downloads/33的Uri共苛,經(jīng)過(guò)Uri截取之后,實(shí)際操作的reference其實(shí)是數(shù)據(jù)庫(kù)中的rowId(數(shù)據(jù)庫(kù)行號(hào))仪吧。

DownloadProvider的調(diào)用處理

在之前版本中庄新,DownloadProvider在插入數(shù)據(jù)后,會(huì)直接以context.startService的方式
來(lái)啟動(dòng)DownloadService薯鼠。進(jìn)行異步任務(wù)下載摄咆。而在Android N版本中引入了JobSchedule組件來(lái)進(jìn)行異步下載任務(wù)的處理。
在Android L版本中引入的JobScheduler可以控制耗電人断,具體使用可以參考:Android JobSchedule工作調(diào)度,
其中DownlaodProvider中的insert方法中的關(guān)鍵操作如下所示:

final long token = Binder.clearCallingIdentity();
try {
  Helpers.scheduleJob(getContext(), rowID);
} finally {
  Binder.restoreCallingIdentity(token);
}

其中Helpers.scheduleJob()方法中使用rowId將那條下載信息查詢出來(lái),然后調(diào)用綁定的DownloadJobService進(jìn)行下載任務(wù)朝蜘。如果線程調(diào)度失敗恶迈,會(huì)返回false。

public static void scheduleJob(Context context, long downloadId) {
    final boolean scheduled = scheduleJob(context, DownloadInfo.queryDownloadInfo(context, downloadId));
    if (!scheduled) {
        // If we didn't schedule a future job, kick off a notification
        // update pass immediately
        getDownloadNotifier(context).update();
    }
}

此時(shí)getDownloadNotifier(context).update()會(huì)將遍歷出所有未刪除的

DownloadJobService調(diào)度執(zhí)行

DownloadService中調(diào)度的線程開(kāi)始下載谱醇,在onStartJob中用rowId查出來(lái)后暇仲,直接開(kāi)線程開(kāi)始下載,具體代碼如下所示:

public boolean onStartJob(JobParameters params) {
  final int id = params.getJobId();
  // Spin up thread to handle this download
  final DownloadInfo info = DownloadInfo.queryDownloadInfo(this, id);
  if (info == null) {
      Log.w(TAG, "Odd, no details found for download " + id);
      return false;
    }
    final DownloadThread thread;
    synchronized (mActiveThreads) {
      thread = new DownloadThread(this, params, info);
      mActiveThreads.put(id, thread);
    }
    thread.start();
    return true;
}
DownloadJobService中的暫停副渴、取消與完成

DownloadJobService中在線程開(kāi)啟后奈附,會(huì)刷新展示相應(yīng)的通知欄,通過(guò)通知欄UI中的相應(yīng)控制煮剧,可以實(shí)現(xiàn)對(duì)于下載任務(wù)的控制斥滤。

  • 在開(kāi)始下載后,當(dāng)點(diǎn)擊取消后勉盅,會(huì)發(fā)送廣播到DownlaodReceiver,當(dāng)接受到這個(gè)廣播后佑颇,會(huì)調(diào)用DownloadManager.remove(downloadIds),而DownloadManager.remove()方法則會(huì)調(diào)用DownloadProvider.delete去刪除記錄任務(wù)草娜。同時(shí)會(huì)依據(jù)rowId移除該線程調(diào)度挑胸。

  • 任務(wù)完成時(shí),會(huì)發(fā)送一個(gè)廣播宰闰,通知下載完成茬贵,但是這里比較意外的是,下載完成的廣播發(fā)送是放在DownloadInfo中調(diào)用DownloadInfo.sendIntentIfRequested()發(fā)送的移袍, 而不是在DownloadThread中解藻。

  • 暫停,比較奇怪的是葡盗,DownloadManager的異步下載線程提供了斷點(diǎn)下載的功能舆逃,寫入文件也會(huì)檢查任務(wù)的下載狀態(tài)是不是暫停,但是,卻并未提供暫停下載任務(wù)的API方法路狮,同時(shí)它的下載狀態(tài)查詢的方法也是私有類型的虫啥。如果需要暫停任務(wù)就需要自定義自己的下載任務(wù)了。

DownloadThread中的斷點(diǎn)下載的實(shí)現(xiàn)方法

其實(shí)在DownloadThread中奄妨,主要的下載方法就是就是線程中的excuteDownload()方法涂籽。部分關(guān)鍵代碼如下:

private void executeDownload() throws StopRequestException {
  final boolean resuming = mInfoDelta.mCurrentBytes != 0;
  ...
  int redirectionCount = 0;
  while (redirectionCount++ < Constants.MAX_REDIRECTS) {
      ......
      conn = (HttpURLConnection) mNetwork.openConnection(url);
      addRequestHeaders(conn, resuming);
      final int responseCode = conn.getResponseCode();
      switch (responseCode) {
          case HTTP_OK:
              if (resuming) {
                  throw new StopRequestException(
                          STATUS_CANNOT_RESUME, "Expected partial, but received OK");
              }
              parseOkHeaders(conn);
              transferData(conn);
              return;
          case HTTP_PARTIAL:
              if (!resuming) {
                  throw new StopRequestException(
                          STATUS_CANNOT_RESUME, "Expected OK, but received partial");
              }
              transferData(conn);
              return;
          ......
      }
      ......
  }
}

在addRequestHeaders()方法中,如果從數(shù)據(jù)庫(kù)中查出的數(shù)據(jù)已讀取寫入文件的字節(jié)數(shù)不為0砸抛,則會(huì)在請(qǐng)求頭前添加一個(gè)rangeconn.addRequestProperty("Range", "bytes=" + mInfoDelta.mCurrentBytes + "-");评雌,當(dāng)添加上此請(qǐng)求頭后,當(dāng)求求成功后直焙,服務(wù)器會(huì)返回HTTP_PARTIAL,將接收到的數(shù)據(jù)通過(guò)transferData()方法寫入到文件中景东。在寫入文件中時(shí),DownloadThread引入了android.drm.DrmManagerClient與android.drm.DrmOutputStream奔誓,這兩個(gè)包位于framework/base/core/drm包下斤吐,部分引用代碼如下所示:

if (DownloadDrmHelper.isDrmConvertNeeded(mInfoDelta.mMimeType)) {
  drmClient = new DrmManagerClient(mContext);
  out = new DrmOutputStream(drmClient, outPfd, mInfoDelta.mMimeType);
} else {
  out = new ParcelFileDescriptor.AutoCloseOutputStream(outPfd);
}

對(duì)于這兩個(gè)類的引入,目前還不是特別熟悉厨喂,后續(xù)研究后會(huì)進(jìn)一步進(jìn)行分析

最后喪心病狂的自己畫個(gè)圖和措,簡(jiǎn)單總結(jié)下DownloadManager的工作流程:整體外源應(yīng)用層通過(guò)FrameWork層DownloadManager API調(diào)用到DownloadProvider,通過(guò)操作數(shù)據(jù)庫(kù),最后通過(guò)DownloadService中的線程調(diào)度完成工作蜕煌。整體上都是由DownloadProvider進(jìn)行過(guò)渡調(diào)用派阱。而數(shù)據(jù)庫(kù)與Service都通過(guò)DownloadProvider進(jìn)行隔離。

簡(jiǎn)單結(jié)構(gòu)圖

DownloadManager中的分析目前就先告一段落斜纪,文中如有分析錯(cuò)誤或描述不清楚之處贫母,請(qǐng)大家留言指出~:)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盒刚,隨后出現(xiàn)的幾起案子颁独,更是在濱河造成了極大的恐慌,老刑警劉巖伪冰,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件誓酒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贮聂,警方通過(guò)查閱死者的電腦和手機(jī)靠柑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吓懈,“玉大人歼冰,你說(shuō)我怎么就攤上這事〕芫” “怎么了隔嫡?”我有些...
    開(kāi)封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵甸怕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我腮恩,道長(zhǎng)梢杭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任秸滴,我火速辦了婚禮武契,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荡含。我一直安慰自己咒唆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布释液。 她就那樣靜靜地躺著全释,像睡著了一般。 火紅的嫁衣襯著肌膚如雪误债。 梳的紋絲不亂的頭發(fā)上浸船,一...
    開(kāi)封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音找前,去河邊找鬼。 笑死判族,一個(gè)胖子當(dāng)著我的面吹牛躺盛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播形帮,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼槽惫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了辩撑?” 一聲冷哼從身側(cè)響起界斜,我...
    開(kāi)封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎合冀,沒(méi)想到半個(gè)月后各薇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡君躺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年峭判,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棕叫。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡林螃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俺泣,到底是詐尸還是另有隱情疗认,我是刑警寧澤完残,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站横漏,受9級(jí)特大地震影響谨设,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绊茧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一铝宵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧华畏,春花似錦鹏秋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仑乌,卻和暖如春百拓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晰甚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工衙传, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厕九。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓蓖捶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扁远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俊鱼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容