zoukankan      html  css  js  c++  java
  • 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()方法了.


            




  • 相关阅读:
    Log4j学习
    HttpURLConnection请求
    正则表达式验证中文、图片上传
    freemarker学习
    参数中带有“&”符号问题
    禁止打印页面
    myEclipse 界面窗口打不开问题
    屏蔽网页右键
    分享功能
    table表格某一td内容太多导致样式混乱的解决方案
  • 原文地址:https://www.cnblogs.com/adm1989/p/4631129.html
Copyright © 2011-2022 走看看