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寫入權限。
   
   
   
   
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
接着,調用DownloadManager.Request開始下載
    
    
    
    
DownloadManagerdownloadManager=(DownloadManager)getSystemService(DOWNLOAD_SERVICE);//文件下載地址String url="http://v.yingshibao.chuanke.com//001_zongshu.mp4";//建立一個Request對象DownloadManager.Request request=newDownloadManager.Request(Uri.parse(url));//設置下載文件路徑request.setDestinationInExternalPublicDir("itbox","zongshu.mp4");//開始下載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排序。
    
    
    
    
class DownloadChangeObserver extends ContentObserver { private Handler handler; private long downloadId; public DownloadChangeObserver(Handler handler, long downloadId) { super(handler); this.handler = handler; this.downloadId = downloadId; } @Override public void onChange(boolean selfChange) { updateView(handler, downloadId); } }
註冊ContentResolver
    
    
    
    
mContext.getContentResolver().registerContentObserver( Uri.parse("content://downloads/my_downloads"), true, new DownloadChangeObserver(handler,downloadId));
updateView()方法,獲取進度,經過handle發送消息,更新UI
    
    
    
    

public void updateView(Handler handler, long downloadId) { // 獲取狀態和字節 int[] bytesAndStatus = getBytesAndStatus(downloadId); // handler.sendMessage(handler.obtainMessage(0, bytesAndStatus[0], bytesAndStatus[1], bytesAndStatus[2]));}public int[] getBytesAndStatus(long downloadId) { int[] bytesAndStatus = new int[] { -1, -1, 0 }; DownloadManager.Query query = new DownloadManager.Query() .setFilterById(downloadId); Cursor c = null; try { c = downloadManager.query(query); if (c != null && c.moveToFirst()) { // 當前下載的字節 bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); // 總字節數 bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); // 狀態 bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); } } finally { if (c != null) { c.close(); } } return bytesAndStatus;}
 
1.3 下載成功監聽
下載完成後,下載管理會發出DownloadManager.ACTION_DOWNLOAD_COMPLETE這個廣播,並傳遞downloadId做爲參數。經過接受廣播咱們能夠打開對下載完成的內容進行操做。
    
    
    
    
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) { Log.v("chadm", "action = " + action); } }};


2.下載流程分析
2.1使用DownloadManager啓動下載流程
     具體流程如時序圖所示:

a.)enqueue執行一個下載任務
文件位置framewok/base/core/java/android/app/DownloadManager.java
   
   
   
   
public long enqueue(Request request) { ContentValues values = request.toContentValues(mPackageName); Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); long id = Long.parseLong(downloadUri.getLastPathSegment()); return id; }
首先將Request對象中包含的信息轉換成ContentValues對象,而後將ContentValues對象插入到DownloadProvider裏面。此方法會返回一個long類型值,此值即爲該條下載記錄的id值

b.)insert,將數據插入到DB對應表裏面
文件位置packages/providers/com.android.providers.downloads/DownloadProvider.java
    
    
    
    
/** * Inserts a row in the database */ @Override public Uri insert(final Uri uri, final ContentValues values) { //檢查權限,若是沒有相應權限,則remove相關數據,插入一條空記錄 checkInsertPermissions(values); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); // note we disallow inserting into ALL_DOWNLOADS int match = sURIMatcher.match(uri); //判斷Uri,只支持對MY_DOWNLOADS的插入操做 if (match != MY_DOWNLOADS) { Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri); throw new IllegalArgumentException("Unknown/Invalid URI " + uri); } // copy some of the input values as it ContentValues filteredValues = new ContentValues(); copyString(Downloads.Impl.COLUMN_URI, values, filteredValues); copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues); copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues); copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues); copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues); copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues); boolean isPublicApi = values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE; // validate the destination column Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION); if (dest != null) { if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) != PackageManager.PERMISSION_GRANTED && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) { throw new SecurityException("setting destination to : " + dest + " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted"); } // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically // switch to non-purgeable download boolean hasNonPurgeablePermission = getContext().checkCallingPermission( Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE) == PackageManager.PERMISSION_GRANTED; if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE && hasNonPurgeablePermission) { dest = Downloads.Impl.DESTINATION_CACHE_PARTITION; } if (dest == Downloads.Impl.DESTINATION_FILE_URI) { getContext().enforcePermission( android.Manifest.permission.WRITE_EXTERNAL_STORAGE, Binder.getCallingPid(), Binder.getCallingUid(), "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI"); checkFileUriDestination(values); } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { getContext().enforcePermission( android.Manifest.permission.ACCESS_CACHE_FILESYSTEM, Binder.getCallingPid(), Binder.getCallingUid(), "need ACCESS_CACHE_FILESYSTEM permission to use system cache"); } filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest); } // validate the visibility column Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY); if (vis == null) { if (dest == Downloads.Impl.DESTINATION_EXTERNAL) { filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); } else { filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_HIDDEN); } } else { filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis); } // copy the control column as is copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues); /* * requests coming from * DownloadManager.addCompletedDownload(String, String, String, * boolean, String, String, long) need special treatment */ if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { // these requests always are marked as 'completed' filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, values.getAsLong(Downloads.Impl.COLUMN_TOTAL_BYTES)); filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues); copyString(Downloads.Impl._DATA, values, filteredValues); copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues); } else { filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); } // set lastupdate to current time long lastMod = mSystemFacade.currentTimeMillis(); filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, lastMod); // use packagename of the caller to set the notification columns String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS); if (pckg != null && (clazz != null || isPublicApi)) { int uid = Binder.getCallingUid(); try { if (uid == 0 || mSystemFacade.userOwnsPackage(uid, pckg)) { filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg); if (clazz != null) { filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz); } } } catch (PackageManager.NameNotFoundException ex) { /* ignored for now */ } } // copy some more columns as is copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues); copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues); copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues); copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues); // UID, PID columns if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) == PackageManager.PERMISSION_GRANTED) { copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues); } filteredValues.put(Constants.UID, Binder.getCallingUid()); if (Binder.getCallingUid() == 0) { copyInteger(Constants.UID, values, filteredValues); } // copy some more columns as is copyStringWithDefault(Downloads.Impl.COLUMN_TITLE, values, filteredValues, ""); copyStringWithDefault(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues, ""); // is_visible_in_downloads_ui column if (values.containsKey(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI)) { copyBoolean(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, values, filteredValues); } else { // by default, make external downloads visible in the UI boolean isExternal = (dest == null || dest == Downloads.Impl.DESTINATION_EXTERNAL); filteredValues.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, isExternal); } // public api requests and networktypes/roaming columns if (isPublicApi) { copyInteger(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, values, filteredValues); copyBoolean(Downloads.Impl.COLUMN_ALLOW_ROAMING, values, filteredValues); copyBoolean(Downloads.Impl.COLUMN_ALLOW_METERED, values, filteredValues); } if (Constants.LOGVV) { Log.v(Constants.TAG, "initiating download with UID " + filteredValues.getAsInteger(Constants.UID)); if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) { Log.v(Constants.TAG, "other UID " + filteredValues.getAsInteger(Downloads.Impl.COLUMN_OTHER_UID)); } } //將數據插入到DB裏面 long rowID = db.insert(DB_TABLE, null, filteredValues); if (rowID == -1) { Log.d(Constants.TAG, "couldn't insert into downloads database"); return null; } //將請求頭數據插入到DB裏面 insertRequestHeaders(db, rowID, values); //通知有內容改變 notifyContentChanged(uri, match); // Always start service to handle notifications and/or scanning final Context context = getContext(); //啓動DownloadService開始下載 context.startService(new Intent(context, DownloadService.class)); return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID); }
DownloadProvider裏面的insert方法裏面,會先檢測相關權限,若是有權限,將表中的各列依次賦值,而後插入到數據庫對應表中,而後啓動DownloadService開始下載。

c.) onStartCommand服務中開始下載任務
文件位置packages/providers/com.android.providers.downloads/DownloadService.java
   
   
   
   
@Override public int onStartCommand(Intent intent, int flags, int startId) { int returnValue = super.onStartCommand(intent, flags, startId); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service onStart"); } mLastStartId = startId; enqueueUpdate(); return returnValue; }
服務啓動後執行enqueueUpdate,此方法會發送一個MSG_UPDATE消息,
   
   
   
   
private void enqueueUpdate() { mUpdateHandler.removeMessages(MSG_UPDATE); mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget(); }
此消息的處理是在
    
    
    
    
private Handler.Callback mUpdateCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final int startId = msg.arg1; if (DEBUG_LIFECYCLE) Log.v(TAG, "Updating for startId " + startId); // Since database is current source of truth, our "active" status // depends on database state. We always get one final update pass // once the real actions have finished and persisted their state. // TODO: switch to asking real tasks to derive active state // TODO: handle media scanner timeouts final boolean isActive; synchronized (mDownloads) { isActive = updateLocked(); } if (msg.what == MSG_FINAL_UPDATE) { // Dump thread stacks belonging to pool for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) { if (entry.getKey().getName().startsWith("pool")) { Log.d(TAG, entry.getKey() + ": " + Arrays.toString(entry.getValue())); } } // Dump speed and update details mNotifier.dumpSpeeds(); Log.wtf(TAG, "Final update pass triggered, isActive=" + isActive + "; someone didn't update correctly."); } if (isActive) { // Still doing useful work, keep service alive. These active // tasks will trigger another update pass when they're finished. // Enqueue delayed update pass to catch finished operations that // didn't trigger an update pass; these are bugs. enqueueFinalUpdate(); } else { // No active tasks, and any pending update messages can be // ignored, since any updates important enough to initiate tasks // will always be delivered with a new startId. if (stopSelfResult(startId)) { if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped"); getContentResolver().unregisterContentObserver(mObserver); mScanner.shutdown(); mUpdateThread.quit(); } } return true; } };
看代碼第18行,isActive = updateLocked();
    
    
    
    
private boolean updateLocked() { final long now = mSystemFacade.currentTimeMillis(); boolean isActive = false; long nextActionMillis = Long.MAX_VALUE; final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet()); final ContentResolver resolver = getContentResolver(); final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null, null, null); try { //更新DB裏面全部下載記錄 final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor); final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); while (cursor.moveToNext()) { final long id = cursor.getLong(idColumn); staleIds.remove(id); DownloadInfo info = mDownloads.get(id); //若是下載信息保存在mDownloads裏面,則直接更新,因爲咱們是新添加的一個任務,info爲空,走insertDownloadLocked這一步 if (info != null) { updateDownload(reader, info, now); } else { //建立一個新的DownloadInfo,而後添加到mDownloads裏面去 info = insertDownloadLocked(reader, now); } if (info.mDeleted) { // Delete download if requested, but only after cleaning up if (!TextUtils.isEmpty(info.mMediaProviderUri)) { resolver.delete(Uri.parse(info.mMediaProviderUri), null, null); } deleteFileIfExists(info.mFileName); resolver.delete(info.getAllDownloadsUri(), null, null); } else { // Kick off download task if ready 準備開始下載 final boolean activeDownload = info.startDownloadIfReady(mExecutor); // Kick off media scan if completed final boolean activeScan = info.startScanIfReady(mScanner); if (DEBUG_LIFECYCLE && (activeDownload || activeScan)) { Log.v(TAG, "Download " + info.mId + ": activeDownload=" + activeDownload + ", activeScan=" + activeScan); } isActive |= activeDownload; isActive |= activeScan; } // Keep track of nearest next action nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis); } } finally { cursor.close(); } // Clean up stale downloads that disappeared for (Long id : staleIds) { deleteDownloadLocked(id); } // Update notifications visible to user mNotifier.updateWith(mDownloads.values()); // Set alarm when next action is in future. It's okay if the service // continues to run in meantime, since it will kick off an update pass. if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) { if (Constants.LOGV) { Log.v(TAG, "scheduling start in " + nextActionMillis + "ms"); } final Intent intent = new Intent(Constants.ACTION_RETRY); intent.setClass(this, DownloadReceiver.class); mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis, PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)); } return isActive; }
此方法會更新DB裏面的全部下載記錄,先獲取全部記錄,而後依次更新。當咱們新添加一條記錄時,會新建立一個DownloadInfo對象,並添加到mDownloads集合裏面;而後調用 DownloadInfo的 startDownloadIfReady方法

d.)  startDownloadIfReady 準備開始下載
文件位置packages/providers/com.android.providers.downloads/DownloadInfo.java
    
    
    
    
public boolean startDownloadIfReady(ExecutorService executor) { synchronized (this) { //判斷是否能夠下載,因爲mControl爲0,返回true final boolean isReady = isReadyToDownload(); //判斷是否有任務正在進行,對象是新建立的,mSubmittedTask 爲空 final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone(); if (isReady && !isActive) { //若是當前狀態不是正在下載,將當前狀態更新爲正在下載 if (mStatus != Impl.STATUS_RUNNING) { mStatus = Impl.STATUS_RUNNING; ContentValues values = new ContentValues(); values.put(Impl.COLUMN_STATUS, mStatus); mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null); } //開始下載任務 mTask = new DownloadThread( mContext, mSystemFacade, this, mStorageManager, mNotifier); mSubmittedTask = executor.submit(mTask); } return isReady; } }
此方法中,先判斷當前狀態是否能夠下載,若是能夠下載,則開始一個任務下載

e.) DownloadThread的run方法
文件位置 packages/providers/com.android.providers.downloads/ DownloadThread .java
    
    
    
    
@Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); try { runInternal(); } finally { mNotifier.notifyDownloadSpeed(mInfo.mId, 0); } } private void runInternal() { // Skip when download already marked as finished; this download was // probably started again while racing with UpdateThread. if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId) == Downloads.Impl.STATUS_SUCCESS) { Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping"); return; } State state = new State(mInfo); PowerManager.WakeLock wakeLock = null; int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR; int numFailed = mInfo.mNumFailed; String errorMsg = null; final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext); final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); try { wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); wakeLock.setWorkSource(new WorkSource(mInfo.mUid)); wakeLock.acquire(); // while performing download, register for rules updates netPolicy.registerListener(mPolicyListener); Log.i(Constants.TAG, "Download " + mInfo.mId + " starting"); // Remember which network this download started on; used to // determine if errors were due to network changes. final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid); if (info != null) { state.mNetworkType = info.getType(); } // Network traffic on this thread should be counted against the // requesting UID, and is tagged with well-known value. TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD); TrafficStats.setThreadStatsUid(mInfo.mUid); try { // TODO: migrate URL sanity checking into client side of API state.mUrl = new URL(state.mRequestUri); } catch (MalformedURLException e) { throw new StopRequestException(STATUS_BAD_REQUEST, e); } //執行下載 executeDownload(state); finalizeDestinationFile(state); finalStatus = Downloads.Impl.STATUS_SUCCESS; } catch (StopRequestException error) { // remove the cause before printing, in case it contains PII errorMsg = error.getMessage(); String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg; Log.w(Constants.TAG, msg); if (Constants.LOGV) { Log.w(Constants.TAG, msg, error); } finalStatus = error.getFinalStatus(); // Nobody below our level should request retries, since we handle // failure counts at this level. if (finalStatus == STATUS_WAITING_TO_RETRY) { throw new IllegalStateException("Execution should always throw final error codes"); } // Some errors should be retryable, unless we fail too many times. if (isStatusRetryable(finalStatus)) { if (state.mGotData) { numFailed = 1; } else { numFailed += 1; } if (numFailed < Constants.MAX_RETRIES) { final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid); if (info != null && info.getType() == state.mNetworkType && info.isConnected()) { // Underlying network is still intact, use normal backoff finalStatus = STATUS_WAITING_TO_RETRY; } else { // Network changed, retry on any next available finalStatus = STATUS_WAITING_FOR_NETWORK; } } } // fall through to finally block } catch (Throwable ex) { errorMsg = ex.getMessage(); String msg = "Exception for id " + mInfo.mId + ": " + errorMsg; Log.w(Constants.TAG, msg, ex); finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR; // falls through to the code that reports an error } finally { if (finalStatus == STATUS_SUCCESS) { TrafficStats.incrementOperationCount(1); } TrafficStats.clearThreadStatsTag(); TrafficStats.clearThreadStatsUid(); cleanupDestination(state, finalStatus); notifyDownloadCompleted(state, finalStatus, errorMsg, numFailed); Log.i(Constants.TAG, "Download " + mInfo.mId + " finished with status " + Downloads.Impl.statusToString(finalStatus)); netPolicy.unregisterListener(mPolicyListener); if (wakeLock != null) { wakeLock.release(); wakeLock = null; } } mStorageManager.incrementNumDownloadsSoFar(); }
啓動任務,裏面會調用runInternal,這個裏面的邏輯很複雜,咱們只關注executeDownload方法
    
    
    
    
/** * Fully execute a single download request. Setup and send the request, * handle the response, and transfer the data to the destination file. */ private void executeDownload(State state) throws StopRequestException { state.resetBeforeExecute(); //設置下載文件相關信息,文件是否存在、是否從0開始下載仍是接着下載 setupDestinationFile(state); // skip when already finished; remove after fixing race in 5217390 if (state.mCurrentBytes == state.mTotalBytes) { Log.i(Constants.TAG, "Skipping initiating request for download " + mInfo.mId + "; already completed"); return; } while (state.mRedirectionCount++ < Constants.MAX_REDIRECTS) { // Open connection and follow any redirects until we have a useful // response with body. HttpURLConnection conn = null; try { checkConnectivity(); conn = (HttpURLConnection) state.mUrl.openConnection(); conn.setInstanceFollowRedirects(false); conn.setConnectTimeout(DEFAULT_TIMEOUT); conn.setReadTimeout(DEFAULT_TIMEOUT); addRequestHeaders(state, conn); final int responseCode = conn.getResponseCode(); switch (responseCode) { case HTTP_OK: if (state.mContinuingDownload) { throw new StopRequestException( STATUS_CANNOT_RESUME, "Expected partial, but received OK"); } processResponseHeaders(state, conn); transferData(state, conn); return; case HTTP_PARTIAL: if (!state.mContinuingDownload) { throw new StopRequestException( STATUS_CANNOT_RESUME, "Expected OK, but received partial"); } transferData(state, conn); return; case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: case HTTP_TEMP_REDIRECT: final String location = conn.getHeaderField("Location"); state.mUrl = new URL(state.mUrl, location); if (responseCode == HTTP_MOVED_PERM) { // Push updated URL back to database state.mRequestUri = state.mUrl.toString(); } continue; case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: throw new StopRequestException( STATUS_CANNOT_RESUME, "Requested range not satisfiable"); case HTTP_UNAVAILABLE: parseRetryAfterHeaders(state, conn); throw new StopRequestException( HTTP_UNAVAILABLE, conn.getResponseMessage()); case HTTP_INTERNAL_ERROR: throw new StopRequestException( HTTP_INTERNAL_ERROR, conn.getResponseMessage()); default: StopRequestException.throwUnhandledHttpError( responseCode, conn.getResponseMessage()); } } catch (IOException e) { // Trouble with low-level sockets throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e); } finally { if (conn != null) conn.disconnect(); } } throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects"); }
addRequestHeaders方法裏面也有一些比較重要的東西
    
    
    
    
private void addRequestHeaders(State state, HttpURLConnection conn) { for (Pair<String, String> header : mInfo.getHeaders()) { conn.addRequestProperty(header.first, header.second); } // Only splice in user agent when not already defined if (conn.getRequestProperty("User-Agent") == null) { conn.addRequestProperty("User-Agent", userAgent()); } // Defeat transparent gzip compression, since it doesn't allow us to // easily resume partial downloads. conn.setRequestProperty("Accept-Encoding", "identity"); if (state.mContinuingDownload) { if (state.mHeaderETag != null) { conn.addRequestProperty("If-Match", state.mHeaderETag); } conn.addRequestProperty("Range", "bytes=" + state.mCurrentBytes + "-"); } }
若是是繼續下載,則把當前的下載進度放在請求頭裏面。

2.2 系統廣播啓動下載
在packages/providers/com.android.providers.downloads/DownloadReceiver.java文件裏面
    
    
    
    
public void onReceive(final Context context, final Intent intent) { if (mSystemFacade == null) { mSystemFacade = new RealSystemFacade(context); } String action = intent.getAction(); if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { if (Constants.LOGVV) { Log.v(Constants.TAG, "Received broadcast intent for " + Intent.ACTION_BOOT_COMPLETED); } startService(context); } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { if (Constants.LOGVV) { Log.v(Constants.TAG, "Received broadcast intent for " + Intent.ACTION_MEDIA_MOUNTED); } startService(context); } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { final ConnectivityManager connManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo info = connManager.getActiveNetworkInfo(); if (info != null && info.isConnected()) { startService(context); } } else if (action.equals(Constants.ACTION_RETRY)) { startService(context); } else if (action.equals(Constants.ACTION_OPEN) || action.equals(Constants.ACTION_LIST) || action.equals(Constants.ACTION_HIDE)) { final PendingResult result = goAsync(); if (result == null) { // TODO: remove this once test is refactored handleNotificationBroadcast(context, intent); } else { sAsyncHandler.post(new Runnable() { @Override public void run() { handleNotificationBroadcast(context, intent); result.finish(); } }); } } }


注意:
       當service第一次被啓動時會調用onCreate()方法, 而後再調用onStartCommand()方法. 在該service的生命週期內, 若是再次啓動這個service, 就會直接調用onStartCommand()方法了.


         


相關文章
相關標籤/搜索