Android 下载模块分析(DownloadManager和DownloadProvider)

Android 下载模块分析(DownloadManager和DownloadProvider)

       Android下载模块主要有2个部分组成:DownloadManager和DownloadProvider;其中DownloadManager提供接口供调用,具体的实现是 DownloadProvider,包括相关数据信息的保存及文件下载。
    
       DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager.Request。
  • DownloadManager.Request用来请求一个下载
  • DownloadManager.Query 用来查询下载信息
DownloadManager主要提供了一下主要方法
  • enqueue(Request request):执行下载,返回downloadId,downloadId可用于查询下载信息。
  • remove(long ids):删除下载,若下载中取消下载。会同时删除下载文件和记录。
  • query(Query query)查询下载信息
  • getMaxBytesOverMobile(Context context)通过移动网络下载的最大字节数
  • getMimeTypeForDownloadedFile(long id)得到下载的mineType
 
    通过查看代码我们可以发现还有个CursorTranslator私有静态内部类。这个类主要对Query做了一层代理。将DownloadProvider和DownloadManager之间做个映射。将DownloadProvider中的十几种状态对应到了DownloadManager中的五种状态,DownloadProvider中的失败、暂停原因转换为了DownloadManager的原因。

1.DownloadManager的一般用法
1.1 调用DownloadManager.Request开始下载
 在开始之前,在AndroidManifest.xml中添加网络访问权限和sdcard写入权限。

  1. <uses-permission android:name="android.permission.INTERNET" />
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
接着,调用DownloadManager.Request开始下载

  1. DownloadManagerdownloadManager=(DownloadManager)getSystemService(DOWNLOAD_SERVICE);
  2. //文件下载地址
  3. String url="http://v.yingshibao.chuanke.com//001_zongshu.mp4";
  4. //创建一个Request对象
  5. DownloadManager.Request request=newDownloadManager.Request(Uri.parse(url));
  6. //设置下载文件路径
  7. request.setDestinationInExternalPublicDir("itbox","zongshu.mp4");
  8. //开始下载
  9. longdownloadId=downloadManager.enqueue(request);
DownloadManager.Request一些常用方法:
  • setDestinationInExternalFilesDir 设置文件下载路径
  • allowScanningByMediaScanner() 表示允许MediaScanner扫描到这个文件夹,默认不允许。
  • setTitle()设置下载中通知栏提示的标题
  • setDescription()设置下载中通知栏提示的介绍。
  • setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)表示下载进行中和下载完成的通知栏是否显示。默认只显示下载中通知。VISIBILITY_VISIBLE_NOTIFY_COMPLETED表示下载完成后显示通知栏提示。VISIBILITY_HIDDEN表示不显示任何通知栏提示,这个需要在AndroidMainfest中添加权限android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
  • request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) 表示下载允许的网络类型,默认在任何网络下都允许下载。有NETWORK_MOBILE、NETWORK_WIFI、NETWORK_BLUETOOTH三种及其组合可供选择。如果只允许wifi下载,而当前网络为3g,则下载会等待。
  • request.setAllowedOverRoaming(boolean allow)移动网络情况下是否允许漫游。
  • request.setMimeType() 设置下载文件的mineType。因为下载管理Ui中点击某个已下载完成文件及下载完成点击通知栏提示都会根据mimeType去打开文件。
  • request.addRequestHeader(String header, String value)添加请求下载的网络链接的http头,比如User-Agent,gzip压缩等
 
1.2 下载进度查询
DownloadManager下载过程中,会将下载的数据和下载的状态插入ContentProvider中,所以我们可以通过注册一个ContentObserver,通过ContentObserver不断获取数据,对UI进行更新。
 
我们主要调用DownloadManager.Query()进行查询,DownloadManager.Query为下载管理对外开放的信息查询类,主要包括以下方法:
* setFilterById(long… ids)根据下载id进行过滤
* setFilterByStatus(int flags)根据下载状态进行过滤
* setOnlyIncludeVisibleInDownloadsUi(boolean value)根据是否在download ui中可见进行过滤。
* orderBy(String column, int direction)根据列进行排序,不过目前仅支持DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP和DownloadManager.COLUMN_TOTAL_SIZE_BYTES排序。

  1. class DownloadChangeObserver extends ContentObserver {
  2. private Handler handler;
  3. private long downloadId;
  4. public DownloadChangeObserver(Handler handler, long downloadId) {
  5. super(handler);
  6. this.handler = handler;
  7. this.downloadId = downloadId;
  8. }
  9. @Override
  10. public void onChange(boolean selfChange) {
  11. updateView(handler, downloadId);
  12. }
  13. }
注册ContentResolver

  1. mContext.getContentResolver().registerContentObserver(
  2. Uri.parse("content://downloads/my_downloads"),
  3. true,
  4. new DownloadChangeObserver(handler,downloadId)
  5. );
updateView()方法,获取进度,通过handle发送消息,更新UI

  1. public void updateView(Handler handler, long downloadId) {
  2. // 获取状态和字节
  3. int[] bytesAndStatus = getBytesAndStatus(downloadId);
  4. //
  5. handler.sendMessage(handler.obtainMessage(0, bytesAndStatus[0],
  6. bytesAndStatus[1], bytesAndStatus[2]));
  7. }
  8. public int[] getBytesAndStatus(long downloadId) {
  9. int[] bytesAndStatus = new int[] { -1, -1, 0 };
  10. DownloadManager.Query query = new DownloadManager.Query()
  11. .setFilterById(downloadId);
  12. Cursor c = null;
  13. try {
  14. c = downloadManager.query(query);
  15. if (c != null && c.moveToFirst()) {
  16. // 当前下载的字节
  17. bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
  18. // 总字节数
  19. bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
  20. // 状态
  21. bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
  22. }
  23. } finally {
  24. if (c != null) {
  25. c.close();
  26. }
  27. }
  28. return bytesAndStatus;
  29. }

 
1.3 下载成功监听
下载完成后,下载管理会发出DownloadManager.ACTION_DOWNLOAD_COMPLETE这个广播,并传递downloadId作为参数。通过接受广播我们可以打开对下载完成的内容进行操作。

  1. registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
  2. BroadcastReceiver receiver = new BroadcastReceiver() {
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. String action = intent.getAction();
  6. if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
  7. Log.v("chadm", "action = " + action);
  8. }
  9. }
  10. };


2.下载流程分析
2.1使用DownloadManager启动下载流程
    具体流程如时序图所示:
Android 下载模块分析(DownloadManager和DownloadProvider)

a.)enqueue执行一个下载任务
文件位置framewok/base/core/java/android/app/DownloadManager.java

  1. public long enqueue(Request request) {
  2. ContentValues values = request.toContentValues(mPackageName);
  3. Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
  4. long id = Long.parseLong(downloadUri.getLastPathSegment());
  5. return id;
  6. }
首先将Request对象中包含的信息转换成ContentValues对象,然后将ContentValues对象插入到DownloadProvider里面。此方法会返回一个long类型值,此值即为该条下载记录的id值

b.)insert,将数据插入到DB对应表里面
文件位置packages/providers/com.android.providers.downloads/DownloadProvider.java

  1. /**
  2. * Inserts a row in the database
  3. */
  4. @Override
  5. public Uri insert(final Uri uri, final ContentValues values) {
  6. //检查权限,如果没有相应权限,则remove相关数据,插入一条空记录
  7. checkInsertPermissions(values);
  8. SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  9. // note we disallow inserting into ALL_DOWNLOADS
  10. int match = sURIMatcher.match(uri);
  11. //判断Uri,只支持对MY_DOWNLOADS的插入操作
  12. if (match != MY_DOWNLOADS) {
  13. Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
  14. throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
  15. }
  16. // copy some of the input values as it
  17. ContentValues filteredValues = new ContentValues();
  18. copyString(Downloads.Impl.COLUMN_URI, values, filteredValues);
  19. copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);
  20. copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
  21. copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);
  22. copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);
  23. copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
  24. boolean isPublicApi =
  25. values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
  26. // validate the destination column
  27. Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
  28. if (dest != null) {
  29. if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
  30. != PackageManager.PERMISSION_GRANTED
  31. && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION
  32. || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
  33. || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) {
  34. throw new SecurityException("setting destination to : " + dest +
  35. " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted");
  36. }
  37. // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically
  38. // switch to non-purgeable download
  39. boolean hasNonPurgeablePermission =
  40. getContext().checkCallingPermission(
  41. Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE)
  42. == PackageManager.PERMISSION_GRANTED;
  43. if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE
  44. && hasNonPurgeablePermission) {
  45. dest = Downloads.Impl.DESTINATION_CACHE_PARTITION;
  46. }
  47. if (dest == Downloads.Impl.DESTINATION_FILE_URI) {
  48. getContext().enforcePermission(
  49. android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
  50. Binder.getCallingPid(), Binder.getCallingUid(),
  51. "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI");
  52. checkFileUriDestination(values);
  53. } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
  54. getContext().enforcePermission(
  55. android.Manifest.permission.ACCESS_CACHE_FILESYSTEM,
  56. Binder.getCallingPid(), Binder.getCallingUid(),
  57. "need ACCESS_CACHE_FILESYSTEM permission to use system cache");
  58. }
  59. filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);
  60. }
  61. // validate the visibility column
  62. Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);
  63. if (vis == null) {
  64. if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {
  65. filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
  66. Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  67. } else {
  68. filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
  69. Downloads.Impl.VISIBILITY_HIDDEN);
  70. }
  71. } else {
  72. filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);
  73. }
  74. // copy the control column as is
  75. copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
  76. /*
  77. * requests coming from
  78. * DownloadManager.addCompletedDownload(String, String, String,
  79. * boolean, String, String, long) need special treatment
  80. */
  81. if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
  82. Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
  83. // these requests always are marked as 'completed'
  84. filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
  85. filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES,
  86. values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES));
  87. filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
  88. copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);
  89. copyString(Downloads.Impl._DATA, values, filteredValues);
  90. copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
  91. } else {
  92. filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
  93. filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
  94. filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
  95. }
  96. // set lastupdate to current time
  97. long lastMod = mSystemFacade.currentTimeMillis();
  98. filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod);
  99. // use packagename of the caller to set the notification columns
  100. String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
  101. String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
  102. if (pckg != null && (clazz != null || isPublicApi)) {
  103. int uid = Binder.getCallingUid();
  104. try {
  105. if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) {
  106. filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);
  107. if (clazz != null) {
  108. filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);
  109. }
  110. }
  111. } catch (PackageManager.NameNotFoundException ex) {
  112. /* ignored for now */
  113. }
  114. }
  115. // copy some more columns as is
  116. copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues);
  117. copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues);
  118. copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues);
  119. copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues);
  120. // UID, PID columns
  121. if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
  122. == PackageManager.PERMISSION_GRANTED) {
  123. copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);
  124. }
  125. filteredValues.put(Constants.UID, Binder.getCallingUid());
  126. if (Binder.getCallingUid() == 0) {
  127. copyInteger(Constants.UID, values, filteredValues);
  128. }
  129. // copy some more columns as is
  130. copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, "");
  131. copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, "");
  132. // is_visible_in_downloads_ui column
  133. if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) {
  134. copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues);
  135. } else {
  136. // by default, make external downloads visible in the UI
  137. boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL);
  138. filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal);
  139. }
  140. // public api requests and networktypes/roaming columns
  141. if (isPublicApi) {
  142. copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues);
  143. copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues);
  144. copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues);
  145. }
  146. if (Constants.LOGVV) {
  147. Log.v(Constants.TAG, "initiating download with UID "
  148. + filteredValues.getAsInteger(Constants.UID));
  149. if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {
  150. Log.v(Constants.TAG, "other UID " +
  151. filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID));
  152. }
  153. }
  154. //将数据插入到DB里面
  155. long rowID = db.insert(DB_TABLE, null, filteredValues);
  156. if (rowID == -1) {
  157. Log.d(Constants.TAG, "couldn't insert into downloads database");
  158. return null;
  159. }
  160. //将请求头数据插入到DB里面
  161. insertRequestHeaders(db, rowID, values);
  162. //通知有内容改变
  163. notifyContentChanged(uri, match);
  164. // Always start service to handle notifications and/or scanning
  165. final Context context = getContext();
  166. //启动DownloadService开始下载
  167. context.startService(new Intent(context, DownloadService.class));
  168. return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
  169. }
DownloadProvider里面的insert方法里面,会先检测相关权限,如果有权限,将表中的各列依次赋值,然后插入到数据库对应表中,然后启动DownloadService开始下载。

c.)onStartCommand服务中开始下载任务
文件位置packages/providers/com.android.providers.downloads/DownloadService.java

  1. @Override
  2. public int onStartCommand(Intent intent, int flags, int startId) {
  3. int returnValue = super.onStartCommand(intent, flags, startId);
  4. if (Constants.LOGVV) {
  5. Log.v(Constants.TAG, "Service onStart");
  6. }
  7. mLastStartId = startId;
  8. enqueueUpdate();
  9. return returnValue;
  10. }
服务启动后执行enqueueUpdate,此方法会发送一个MSG_UPDATE消息,

  1. private void enqueueUpdate() {
  2. mUpdateHandler.removeMessages(MSG_UPDATE);
  3. mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();
  4. }
此消息的处理是在

  1. private Handler.Callback mUpdateCallback = new Handler.Callback() {
  2. @Override
  3. public boolean handleMessage(Message msg) {
  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  5. final int startId = msg.arg1;
  6. if (DEBUG_LIFECYCLE) Log.v(TAG, "Updating for startId " + startId);
  7. // Since database is current source of truth, our "active" status
  8. // depends on database state. We always get one final update pass
  9. // once the real actions have finished and persisted their state.
  10. // TODO: switch to asking real tasks to derive active state
  11. // TODO: handle media scanner timeouts
  12. final boolean isActive;
  13. synchronized (mDownloads) {
  14. isActive = updateLocked();
  15. }
  16. if (msg.what == MSG_FINAL_UPDATE) {
  17. // Dump thread stacks belonging to pool
  18. for (Map.Entry<Thread, StackTraceElement[]> entry :
  19. Thread.getAllStackTraces().entrySet()) {
  20. if (entry.getKey().getName().startsWith("pool")) {
  21. Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue()));
  22. }
  23. }
  24. // Dump speed and update details
  25. mNotifier.dumpSpeeds();
  26. Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive
  27. + "; someone didn't update correctly.");
  28. }
  29. if (isActive) {
  30. // Still doing useful work, keep service alive. These active
  31. // tasks will trigger another update pass when they're finished.
  32. // Enqueue delayed update pass to catch finished operations that
  33. // didn't trigger an update pass; these are bugs.
  34. enqueueFinalUpdate();
  35. } else {
  36. // No active tasks, and any pending update messages can be
  37. // ignored, since any updates important enough to initiate tasks
  38. // will always be delivered with a new startId.
  39. if (stopSelfResult(startId)) {
  40. if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
  41. getContentResolver().unregisterContentObserver(mObserver);
  42. mScanner.shutdown();
  43. mUpdateThread.quit();
  44. }
  45. }
  46. return true;
  47. }
  48. };
看代码第18行,isActive = updateLocked();

  1. private boolean updateLocked() {
  2. final long now = mSystemFacade.currentTimeMillis();
  3. boolean isActive = false;
  4. long nextActionMillis = Long.MAX_VALUE;
  5. final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());
  6. final ContentResolver resolver = getContentResolver();
  7. final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
  8. null, null, null, null);
  9. try {
  10. //更新DB里面所有下载记录
  11. final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
  12. final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);
  13. while (cursor.moveToNext()) {
  14. final long id = cursor.getLong(idColumn);
  15. staleIds.remove(id);
  16. DownloadInfo info = mDownloads.get(id);
  17. //如果下载信息保存在mDownloads里面,则直接更新,由于我们是新添加的一个任务,info为空,走insertDownloadLocked这一步
  18. if (info != null) {
  19. updateDownload(reader, info, now);
  20. } else {
  21. //创建一个新的DownloadInfo,然后添加到mDownloads里面去
  22. info = insertDownloadLocked(reader, now);
  23. }
  24. if (info.mDeleted) {
  25. // Delete download if requested, but only after cleaning up
  26. if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
  27. resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
  28. }
  29. deleteFileIfExists(info.mFileName);
  30. resolver.delete(info.getAllDownloadsUri(), null, null);
  31. } else {
  32. // Kick off download task if ready 准备开始下载
  33. final boolean activeDownload = info.startDownloadIfReady(mExecutor);
  34. // Kick off media scan if completed
  35. final boolean activeScan = info.startScanIfReady(mScanner);
  36. if (DEBUG_LIFECYCLE && (activeDownload || activeScan)) {
  37. Log.v(TAG, "Download " + info.mId + ": activeDownload=" + activeDownload
  38. + ", activeScan=" + activeScan);
  39. }
  40. isActive |= activeDownload;
  41. isActive |= activeScan;
  42. }
  43. // Keep track of nearest next action
  44. nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);
  45. }
  46. } finally {
  47. cursor.close();
  48. }
  49. // Clean up stale downloads that disappeared
  50. for (Long id : staleIds) {
  51. deleteDownloadLocked(id);
  52. }
  53. // Update notifications visible to user
  54. mNotifier.updateWith(mDownloads.values());
  55. // Set alarm when next action is in future. It's okay if the service
  56. // continues to run in meantime, since it will kick off an update pass.
  57. if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {
  58. if (Constants.LOGV) {
  59. Log.v(TAG, "scheduling start in " + nextActionMillis + "ms");
  60. }
  61. final Intent intent = new Intent(Constants.ACTION_RETRY);
  62. intent.setClass(this, DownloadReceiver.class);
  63. mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis,
  64. PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT));
  65. }
  66. return isActive;
  67. }
此方法会更新DB里面的所有下载记录,先获取所有记录,然后依次更新。当我们新添加一条记录时,会新创建一个DownloadInfo对象,并添加到mDownloads集合里面;然后调用DownloadInfo的startDownloadIfReady方法

d.) startDownloadIfReady 准备开始下载
文件位置packages/providers/com.android.providers.downloads/DownloadInfo.java

  1. public boolean startDownloadIfReady(ExecutorService executor) {
  2. synchronized (this) {
  3. //判断是否可以下载,由于mControl为0,返回true
  4. final boolean isReady = isReadyToDownload();
  5. //判断是否有任务正在进行,对象是新创建的,mSubmittedTask 为空
  6. final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
  7. if (isReady && !isActive) {
  8. //如果当前状态不是正在下载,将当前状态更新为正在下载
  9. if (mStatus != Impl.STATUS_RUNNING) {
  10. mStatus = Impl.STATUS_RUNNING;
  11. ContentValues values = new ContentValues();
  12. values.put(Impl.COLUMN_STATUS, mStatus);
  13. mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
  14. }
  15. //开始下载任务
  16. mTask = new DownloadThread(
  17. mContext, mSystemFacade, this, mStorageManager, mNotifier);
  18. mSubmittedTask = executor.submit(mTask);
  19. }
  20. return isReady;
  21. }
  22. }
此方法中,先判断当前状态是否可以下载,如果可以下载,则开始一个任务下载

e.) DownloadThread的run方法
文件位置packages/providers/com.android.providers.downloads/DownloadThread.java

  1. @Override
  2. public void run() {
  3. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  4. try {
  5. runInternal();
  6. } finally {
  7. mNotifier.notifyDownloadSpeed(mInfo.mId, 0);
  8. }
  9. }
  10. private void runInternal() {
  11. // Skip when download already marked as finished; this download was
  12. // probably started again while racing with UpdateThread.
  13. if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId)
  14. == Downloads.Impl.STATUS_SUCCESS) {
  15. Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping");
  16. return;
  17. }
  18. State state = new State(mInfo);
  19. PowerManager.WakeLock wakeLock = null;
  20. int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
  21. int numFailed = mInfo.mNumFailed;
  22. String errorMsg = null;
  23. final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);
  24. final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
  25. try {
  26. wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
  27. wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
  28. wakeLock.acquire();
  29. // while performing download, register for rules updates
  30. netPolicy.registerListener(mPolicyListener);
  31. Log.i(Constants.TAG, "Download " + mInfo.mId + " starting");
  32. // Remember which network this download started on; used to
  33. // determine if errors were due to network changes.
  34. final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
  35. if (info != null) {
  36. state.mNetworkType = info.getType();
  37. }
  38. // Network traffic on this thread should be counted against the
  39. // requesting UID, and is tagged with well-known value.
  40. TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);
  41. TrafficStats.setThreadStatsUid(mInfo.mUid);
  42. try {
  43. // TODO: migrate URL sanity checking into client side of API
  44. state.mUrl = new URL(state.mRequestUri);
  45. } catch (MalformedURLException e) {
  46. throw new StopRequestException(STATUS_BAD_REQUEST, e);
  47. }
  48. //执行下载
  49. executeDownload(state);
  50. finalizeDestinationFile(state);
  51. finalStatus = Downloads.Impl.STATUS_SUCCESS;
  52. } catch (StopRequestException error) {
  53. // remove the cause before printing, in case it contains PII
  54. errorMsg = error.getMessage();
  55. String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg;
  56. Log.w(Constants.TAG, msg);
  57. if (Constants.LOGV) {
  58. Log.w(Constants.TAG, msg, error);
  59. }
  60. finalStatus = error.getFinalStatus();
  61. // Nobody below our level should request retries, since we handle
  62. // failure counts at this level.
  63. if (finalStatus == STATUS_WAITING_TO_RETRY) {
  64. throw new IllegalStateException("Execution should always throw final error codes");
  65. }
  66. // Some errors should be retryable, unless we fail too many times.
  67. if (isStatusRetryable(finalStatus)) {
  68. if (state.mGotData) {
  69. numFailed = 1;
  70. } else {
  71. numFailed += 1;
  72. }
  73. if (numFailed < Constants.MAX_RETRIES) {
  74. final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
  75. if (info != null && info.getType() == state.mNetworkType
  76. && info.isConnected()) {
  77. // Underlying network is still intact, use normal backoff
  78. finalStatus = STATUS_WAITING_TO_RETRY;
  79. } else {
  80. // Network changed, retry on any next available
  81. finalStatus = STATUS_WAITING_FOR_NETWORK;
  82. }
  83. }
  84. }
  85. // fall through to finally block
  86. } catch (Throwable ex) {
  87. errorMsg = ex.getMessage();
  88. String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;
  89. Log.w(Constants.TAG, msg, ex);
  90. finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
  91. // falls through to the code that reports an error
  92. } finally {
  93. if (finalStatus == STATUS_SUCCESS) {
  94. TrafficStats.incrementOperationCount(1);
  95. }
  96. TrafficStats.clearThreadStatsTag();
  97. TrafficStats.clearThreadStatsUid();
  98. cleanupDestination(state, finalStatus);
  99. notifyDownloadCompleted(state, finalStatus, errorMsg, numFailed);
  100. Log.i(Constants.TAG, "Download " + mInfo.mId + " finished with status "
  101. + Downloads.Impl.statusToString(finalStatus));
  102. netPolicy.unregisterListener(mPolicyListener);
  103. if (wakeLock != null) {
  104. wakeLock.release();
  105. wakeLock = null;
  106. }
  107. }
  108. mStorageManager.incrementNumDownloadsSoFar();
  109. }
启动任务,里面会调用runInternal,这个里面的逻辑很复杂,我们只关注executeDownload方法

  1. /**
  2. * Fully execute a single download request. Setup and send the request,
  3. * handle the response, and transfer the data to the destination file.
  4. */
  5. private void executeDownload(State state) throws StopRequestException {
  6. state.resetBeforeExecute();
  7. //设置下载文件相关信息,文件是否存在、是否从0开始下载还是接着下载
  8. setupDestinationFile(state);
  9. // skip when already finished; remove after fixing race in 5217390
  10. if (state.mCurrentBytes == state.mTotalBytes) {
  11. Log.i(Constants.TAG, "Skipping initiating request for download " +
  12. mInfo.mId + "; already completed");
  13. return;
  14. }
  15. while (state.mRedirectionCount++ < Constants.MAX_REDIRECTS) {
  16. // Open connection and follow any redirects until we have a useful
  17. // response with body.
  18. HttpURLConnection conn = null;
  19. try {
  20. checkConnectivity();
  21. conn = (HttpURLConnection) state.mUrl.openConnection();
  22. conn.setInstanceFollowRedirects(false);
  23. conn.setConnectTimeout(DEFAULT_TIMEOUT);
  24. conn.setReadTimeout(DEFAULT_TIMEOUT);
  25. addRequestHeaders(state, conn);
  26. final int responseCode = conn.getResponseCode();
  27. switch (responseCode) {
  28. case HTTP_OK:
  29. if (state.mContinuingDownload) {
  30. throw new StopRequestException(
  31. STATUS_CANNOT_RESUME, "Expected partial, but received OK");
  32. }
  33. processResponseHeaders(state, conn);
  34. transferData(state, conn);
  35. return;
  36. case HTTP_PARTIAL:
  37. if (!state.mContinuingDownload) {
  38. throw new StopRequestException(
  39. STATUS_CANNOT_RESUME, "Expected OK, but received partial");
  40. }
  41. transferData(state, conn);
  42. return;
  43. case HTTP_MOVED_PERM:
  44. case HTTP_MOVED_TEMP:
  45. case HTTP_SEE_OTHER:
  46. case HTTP_TEMP_REDIRECT:
  47. final String location = conn.getHeaderField("Location");
  48. state.mUrl = new URL(state.mUrl, location);
  49. if (responseCode == HTTP_MOVED_PERM) {
  50. // Push updated URL back to database
  51. state.mRequestUri = state.mUrl.toString();
  52. }
  53. continue;
  54. case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
  55. throw new StopRequestException(
  56. STATUS_CANNOT_RESUME, "Requested range not satisfiable");
  57. case HTTP_UNAVAILABLE:
  58. parseRetryAfterHeaders(state, conn);
  59. throw new StopRequestException(
  60. HTTP_UNAVAILABLE, conn.getResponseMessage());
  61. case HTTP_INTERNAL_ERROR:
  62. throw new StopRequestException(
  63. HTTP_INTERNAL_ERROR, conn.getResponseMessage());
  64. default:
  65. StopRequestException.throwUnhandledHttpError(
  66. responseCode, conn.getResponseMessage());
  67. }
  68. } catch (IOException e) {
  69. // Trouble with low-level sockets
  70. throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);
  71. } finally {
  72. if (conn != null) conn.disconnect();
  73. }
  74. }
  75. throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");
  76. }
addRequestHeaders方法里面也有一些比较重要的东西

  1. private void addRequestHeaders(State state, HttpURLConnection conn) {
  2. for (Pair<String, String> header : mInfo.getHeaders()) {
  3. conn.addRequestProperty(header.first, header.second);
  4. }
  5. // Only splice in user agent when not already defined
  6. if (conn.getRequestProperty("User-Agent") == null) {
  7. conn.addRequestProperty("User-Agent", userAgent());
  8. }
  9. // Defeat transparent gzip compression, since it doesn't allow us to
  10. // easily resume partial downloads.
  11. conn.setRequestProperty("Accept-Encoding", "identity");
  12. if (state.mContinuingDownload) {
  13. if (state.mHeaderETag != null) {
  14. conn.addRequestProperty("If-Match", state.mHeaderETag);
  15. }
  16. conn.addRequestProperty("Range", "bytes=" + state.mCurrentBytes + "-");
  17. }
  18. }
如果是继续下载,则把当前的下载进度放在请求头里面。

2.2 系统广播启动下载
在packages/providers/com.android.providers.downloads/DownloadReceiver.java文件里面

  1. public void onReceive(final Context context, final Intent intent) {
  2. if (mSystemFacade == null) {
  3. mSystemFacade = new RealSystemFacade(context);
  4. }
  5. String action = intent.getAction();
  6. if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
  7. if (Constants.LOGVV) {
  8. Log.v(Constants.TAG, "Received broadcast intent for " +
  9. Intent.ACTION_BOOT_COMPLETED);
  10. }
  11. startService(context);
  12. } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
  13. if (Constants.LOGVV) {
  14. Log.v(Constants.TAG, "Received broadcast intent for " +
  15. Intent.ACTION_MEDIA_MOUNTED);
  16. }
  17. startService(context);
  18. } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
  19. final ConnectivityManager connManager = (ConnectivityManager) context
  20. .getSystemService(Context.CONNECTIVITY_SERVICE);
  21. final NetworkInfo info = connManager.getActiveNetworkInfo();
  22. if (info != null && info.isConnected()) {
  23. startService(context);
  24. }
  25. } else if (action.equals(Constants.ACTION_RETRY)) {
  26. startService(context);
  27. } else if (action.equals(Constants.ACTION_OPEN)
  28. || action.equals(Constants.ACTION_LIST)
  29. || action.equals(Constants.ACTION_HIDE)) {
  30. final PendingResult result = goAsync();
  31. if (result == null) {
  32. // TODO: remove this once test is refactored
  33. handleNotificationBroadcast(context, intent);
  34. } else {
  35. sAsyncHandler.post(new Runnable() {
  36. @Override
  37. public void run() {
  38. handleNotificationBroadcast(context, intent);
  39. result.finish();
  40. }
  41. });
  42. }
  43. }
  44. }


注意:
       当service第一次被启动时会调用onCreate()方法, 然后再调用onStartCommand()方法. 在该service的生命周期内, 如果再次启动这个service, 就会直接调用onStartCommand()方法了.


        




posted on 2015-07-08 19:08 adm1989 阅读(...) 评论(...) 编辑 收藏