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启动下载流程

具体流程如时序图所示:

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()方法了.

来自为知笔记(Wiz)

时间: 2024-10-11 06:51:49

Android 下载模块分析(DownloadManager和DownloadProvider)的相关文章

Android WIFI模块分析

一:什么是WIFI WIFI是一种无线连接技术,可用于手机.电脑.PDA等终端.WIFI技术产生的目的是改善基于IEEE802.11标准的无线网络产品之间的互通性,也就是说WIFI是基于802.11标准的,但WIFI不等同无线网络. 二:Android平台下的WIFI模块 简单介绍一下,WIFI模块的基本功能: 1. 开关WIFI 除了在WIFI设置界面可以开关WIFI,还有其他的方法可以设置,要查看这些开关状态是否一致.还有就是飞行模式对WIFI开关的影响,由于WIFI开和关都有一个时间过程,

Android 源码分析工具

标 题: [原创]Android源码分析工具及方法作 者: MindMac时 间: 2014-01-02,09:32:35链 接: http://bbs.pediy.com/showthread.php?t=183278 在对 Android 源码进行分析时,如果有得力的工具辅助,会达到事半功倍的效果.本文介绍了一些在分析 Android 源码时使用的一些工具和方法,希望能够帮助到有需要的同学. Eclipse 在 Android 应用程序开发过程中,一般会使用 Eclipse,当然 Googl

Android源码分析:Telephony部分–GSMPhone

Android源码分析:Telephony部分–GSMPhone红狼博客 PhoneProxy/GSMPhone/CDMAPhone 如果说RILJ提供了工具或管道,那么Phone接口的子类及PhoneFactory则为packages/app/Phone这个应用程序进程使用RILJ这个工具或管道提供了极大的方便,它们一个管理整个整个手机的Telephony功能. GSMPhone和CDMAPhone实现了Phone中定义的接口.接口类Phone定义了一套API,这套API用于使用RILJ(见后

Android内存管理分析

大部分因为工作任务繁重,一般我们很少关心内存的事,只知道先把任务完成.只有真正到了发现UI卡顿 或者APP实在跑不下去了(一点一卡),才会考虑到内存优化.或者你所在的大公司比较关心手机运行流利程度,也需要对内存进行管理. 1.内存管理的基础知识 因为安卓的顶层也是 Java来实现的,作为客户顿的程序员应该懂得如何去管理内存. 又因为Java不像C语言可以执行free去主动释放内存,而是提供了一套Java的垃圾处理器.但是该处理器并不能时刻盯着内存,在内存不需要的时候直接清理(程序员比较方便,但是

CM android的CMUpdater分析(二)

至于为何要在这里讲解android系统源码中的系统更新,我已经在上一篇< CM android的CMUpdater分析(一)>中介绍了.在上一篇中,主要讲解了在eclipse中如何搭建系统应用的开发环境,现在我们就使用eclipse来分析CMUpdater源码.该系统更新是CM修改原生android的基础上实现的.通过分析android系统的应用源码,可以学到一些很好的思想和编程方法学.好了,废话少说,现在就开始我们的学习之旅. 首先,在开始介绍之前,我先把之前根据CMUpdater源码分析来

Cordova Android源码分析系列一(项目总览和CordovaActivity分析)

PhoneGap/Cordova是一个专业的移动应用开发框架,是一个全面的WEB APP开发的框架,提供了以WEB形式来访问终端设备的API的功能.这对于采用WEB APP进行开发者来说是个福音,这可以避免了原生开发的某些功能.Cordova 只是个原生外壳,app的内核是一个完整的webapp,需要调用的原生功能将以原生插件的形式实现,以暴露js接口的方式调用. Cordova Android项目是Cordova Android原生部分的Java代码实现,提供了Android原生代码和上层We

android 内存泄漏分析技巧

java虚拟机运行一般都有一个内存界限,超过这个界限,就会报outofmemory.这个时候一般都是存在内存泄漏.解决内存泄漏问题,窃以为分为两个步骤:分析应用程序是否真的有内存泄漏,找到内存泄漏的地方.这两个步骤都不是一般意义上的调试,直接打log,断点调试都不是太给力.动脑筋想一想,内存问题应该在很多地方上都会出现,这么常见的问题应该是有工具的.android现在更可以说是一个生态系统,当然也有很多开发辅助工具.在前面的两个步骤中都有很强大的武器,熟练的掌握这些利器,分析问题就会事半功倍.

ABP之模块分析

ABP之模块分析 本篇作为我ABP介绍的第三篇文章,这次想讲下模块的,ABP文档已经有模块这方面的介绍,但是它只讲到如何使用模块,我想详细讲解下它模块的设计思路. ABP 框架提供了创建和组装模块的基础,一个模块能够依赖于另一个模块.在通常情况 下,一个程序集就可以看成是一个模块.在 ABP 框架中,一个模块通过一个类来定义,而这 个类要继承自 AbpModule. 其实它的设计思路很简单: 1.加载bin目录下的所有dll 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Android init源代码分析(2)init.rc解析

本文描述init.rc脚本解析以及执行过程,读完本章后,读者应能 (1) 了解init.rc解析过程 (2) 定制init.rc init.rc介绍 init.rc是一个文本文件,可认为它是Android系统启动脚本.init.rc文件中定义了环境变量配置.系统进程启动,分区挂载,属性配置等诸多内容.init.rc具有特殊的语法.init源码目录下的readme.txt中详细的描述了init启动脚本的语法规则,是试图定制init.rc的开发者的必读资料. Android启动脚本包括一组文件,包括