Android DownloadProvider source code analysis:
Download's source code compilation is divided into two parts, one is and the other is.
The source codes of these two apks are located in
packages/providers/DownloadProvider/ui/src
packages/providers/DownloadProvider/src
Among them, the DownloadProvider part is the implementation of the download logic, while DownloadProviderUi is the implementation of the interface part.
Then, although the download in DownloadProvider is mainly operated through DownloadService, it involves updates of Notification, display of download progress, download management, etc.
So there are still many other classes to operate separately.
DownloadProvider -- The encapsulation of database operations, inherited from ContentProvider;
DownloadManager -- Most of the logic is to further encapsulate data operations for external calls;
DownloadService -- encapsulates files download, delete and other operations, and manipulates the downloaded norification; inherits from Service;
DownloadNotifier -- Status bar Notification logic;
DownloadReceiver -- cooperates with DownloadNotifier to perform file operations and Notification;
DownloadList -- Download app main interface, file interface interaction;
Download usually starts by clicking on the link in Browser. Let’s take a look at the code in Browser.
In browser's src/com/Android/browser/ function, we can see a very complete download call. When we write our own app, we can also refer to this paragraph:
public static void startingDownload(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, String referer, boolean privateBrowsing, long contentLength, String filename, String downloadPath) { // is a lot stricter than KURL so we have to encode some // extra characters. Fix for b 2538060 and b 1634719 WebAddress webAddress; try { webAddress = new WebAddress(url); (encodePath(())); } catch (Exception e) { // This only happens for very bad urls, we want to chatch the // exception here (LOGTAG, "Exception trying to parse url:" + url); return; } String addressString = (); Uri uri = (addressString); final request; try { request = new (uri); } catch (IllegalArgumentException e) { (activity, .cannot_download, Toast.LENGTH_SHORT).show(); return; } (mimetype); // set downloaded file destination to /sdcard/Download. // or, should it be set to one of several * dirs // depending on mimetype? try { setDestinationDir(downloadPath, filename, request); } catch (Exception e) { showNoEnoughMemoryDialog(activity); return; } // let this downloaded file be scanned by MediaScanner - so that it can // show up in Gallery app, for example. (); (()); // XXX: Have to use the old url since the cookies were stored using the // old percent-encoded url. String cookies = ().getCookie(url, privateBrowsing); ("cookie", cookies); ("User-Agent", userAgent); ("Referer", referer); ( .VISIBILITY_VISIBLE_NOTIFY_COMPLETED); final DownloadManager manager = (DownloadManager) activity .getSystemService(Context.DOWNLOAD_SERVICE); new Thread("Browser download") { public void run() { (request); } }.start(); showStartDownloadToast(activity); }
In this operation, we see that various parameters of the request were added, and then finally called the enqueue of the DownloadManager to download. After the start, the toast that started downloading pops up. Manager is an instance of DownloadManager, which exists with frameworks/base/core/java/android/app/. You can see that the implementation of enqueue is:
public long enqueue(Request request) { ContentValues values = (mPackageName); Uri downloadUri = (.CONTENT_URI, values); long id = (()); return id;
The enqueue function mainly decomposes the Rquest instance into a ContentValues instance and adds it to the database. The function returns the ID returned by the inserted data; the function will be called into the insert function of the ContentProvider implemented by DownloadProvider. If we check the insert code, we can see that there are many operations. But we only need to focus on a few key parts:
...... //Insert the relevant request parameters, configurations, etc. into the downloads database;long rowID = (DB_TABLE, null, filteredValues); ...... //Insert the relevant request parameters, configurations, etc. into the request_headers database;insertRequestHeaders(db, rowID, values); ...... if ((.COLUMN_DESTINATION) == .DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { // When notification is requested, kick off service to process all // relevant downloads. //Start DownloadService for download and other work if ((vis)) { (new Intent(context, )); } } else { (new Intent(context, )); } notifyContentChanged(uri, match); return (.CONTENT_URI, rowID);
Here we can see the downloaded DownloadService call. Because it is a startService method, we need to go to the oncreate method in DownloadService.
@Override public void onCreate() { (); if () { (, "Service onCreate"); } if (mSystemFacade == null) { mSystemFacade = new RealSystemFacade(this); } mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mStorageManager = new StorageManager(this); mUpdateThread = new HandlerThread(TAG + "-UpdateThread"); (); mUpdateHandler = new Handler((), mUpdateCallback); mScanner = new DownloadScanner(this); mNotifier = new DownloadNotifier(this); (); mObserver = new DownloadManagerContentObserver(); getContentResolver().registerContentObserver(.ALL_DOWNLOADS_CONTENT_URI, true, mObserver); }
Here, we can see that we start a handler to receive callback processing
mUpdateThread = new HandlerThread(TAG + "-UpdateThread"); (); mUpdateHandler = new Handler((), mUpdateCallback);
Then go
getContentResolver().registerContentObserver(.ALL_DOWNLOADS_CONTENT_URI, true, mObserver)
It is to register and listen to the Observer for .ALL_DOWNLOADS_CONTENT_URI.
After oncreate, the onStartCommand method will be called.
@Override ublic int onStartCommand(Intent intent, int flags, int startId) { int returnValue = (intent, flags, startId); if () { (, "Service onStart"); } mLastStartId = startId; enqueueUpdate(); return returnValue; }
In the enqueueUpdate function, we will send an MSG_UPDATE Message to mUpdateHandler.
private void enqueueUpdate() { (MSG_UPDATE); (MSG_UPDATE, mLastStartId, -1).sendToTarget(); }
Received and processed in mUpdateCallback:
private mUpdateCallback = new () { @Override public boolean handleMessage(Message msg) { (Process.THREAD_PRIORITY_BACKGROUND); final int startId = msg.arg1; final boolean isActive; synchronized (mDownloads) { isActive = updateLocked(); } ...... if (isActive) { //If Active, MSG_FINAL_UPDATE Message will be sent after Delayed 5×60000ms, mainly for "any finished operations that didn't trigger an update pass." enqueueFinalUpdate(); } else { //If no Active task is in progress, the Service and other tasks will be stopped if (stopSelfResult(startId)) { if (DEBUG_LIFECYCLE) (TAG, "Nothing left; stopped"); getContentResolver().unregisterContentObserver(mObserver); (); (); } } return true; } };
The focus here is the updateLocked() function
private boolean updateLocked() { final long now = (); boolean isActive = false; long nextActionMillis = Long.MAX_VALUE; //mDownloads initialization is an empty Map<Long, DownloadInfo> final Set<Long> staleIds = (()); final ContentResolver resolver = getContentResolver(); //Get all DOWNLOADS tasks final Cursor cursor = (.ALL_DOWNLOADS_CONTENT_URI, null, null, null, null); try { final reader = new (resolver, cursor); final int idColumn = (._ID); //Iteration Download Cursor while (()) { final long id = (idColumn); (id); DownloadInfo info = (id); // At the beginning, mDownloads has nothing to do with, info==null if (info != null) { //Update the latest Download info information from the database to listen to the database changes and reflect them to the interface updateDownload(reader, info, now); } else { //Add the newly downloaded DVDload info to mDownloads and read the new DVDload info from the database info = insertDownloadLocked(reader, now); } //The mDeleted parameter here means that when I delete the content that is being downloaded or has been downloaded, the database will first update this to true, instead of directly deleting the file if () { //I won't explain the delete function in detail, mainly to delete the database content and current file content if (!()) { ((), null, null); } deleteFileIfExists(); ((), null, null); } else { // Start downloading the file final boolean activeDownload = (mExecutor); // Start media scanner final boolean activeScan = (mScanner); isActive |= activeDownload; isActive |= activeScan; } // Keep track of nearest next action nextActionMillis = ((now), nextActionMillis); } } finally { (); } // Clean up stale downloads that disappeared for (Long id : staleIds) { deleteDownloadLocked(id); } // Update notifications visible to user (()); if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) { final Intent intent = new Intent(Constants.ACTION_RETRY); (this, ); (AlarmManager.RTC_WAKEUP, now + nextActionMillis, (this, 0, intent, PendingIntent.FLAG_ONE_SHOT)); } return isActive; }
Focus on the download of files, startDownloadIfReady function:
public boolean startDownloadIfReady(ExecutorService executor) { synchronized (this) { final boolean isReady = isReadyToDownload(); final boolean isActive = mSubmittedTask != null && !(); if (isReady && !isActive) { //The task status of the update database is STATUS_RUNNING if (mStatus != Impl.STATUS_RUNNING) { mStatus = Impl.STATUS_RUNNING; ContentValues values = new ContentValues(); (Impl.COLUMN_STATUS, mStatus); ().update(getAllDownloadsUri(), values, null, null); } //Start download task mTask = new DownloadThread( mContext, mSystemFacade, this, mStorageManager, mNotifier); mSubmittedTask = (mTask); } return isReady; } }
In the process of DownloadThread, if the HTTP status is OK, transferDate will be processed.
private void transferData(State state, HttpURLConnection conn) throws StopRequestException { ...... in = (); ...... //Get InputStream and OutPutStreamif (()) { drmClient = new DrmManagerClient(mContext); final RandomAccessFile file = new RandomAccessFile( new File(), "rw"); out = new DrmOutputStream(drmClient, file, ); outFd = (); } else { out = new FileOutputStream(, true); outFd = ((FileOutputStream) out).getFD(); } ...... // Start streaming data, periodically watch for pause/cancel // commands and checking disk space as needed. transferData(state, in, out); ...... }
------
private void transferData(State state, InputStream in, OutputStream out) throws StopRequestException { final byte data[] = new byte[Constants.BUFFER_SIZE]; for (;;) { //Read content information from InputStream, "(data)" and update the file download size in the database int bytesRead = readFromResponse(state, data, in); if (bytesRead == -1) { // success, end of stream already reached handleEndOfStream(state); return; } = true; //Use OutPutStream to write to the read InputStream, "(data, 0, bytesRead)" writeDataToDestination(state, data, bytesRead, out); += bytesRead; reportProgress(state); } checkPausedOrCanceled(state); } }
At this point, the process of downloading the file is finished. Continue to return to the updateLocked() function of DownloadService; focus on analyzing the updateWith() function of DownloadNotifier, which is used to update Notification.
//This code is to set different Notification icon according to different states if (type == TYPE_ACTIVE) { (.stat_sys_download); } else if (type == TYPE_WAITING) { (.stat_sys_warning); } else if (type == TYPE_COMPLETE) { (.stat_sys_download_done); }
//This code sets different notification Intents according to different states// Build action intents if (type == TYPE_ACTIVE || type == TYPE_WAITING) { // build a synthetic uri for intent identification purposes final Uri uri = new ().scheme("active-dl").appendPath(tag).build(); final Intent intent = new Intent(Constants.ACTION_LIST, uri, mContext, ); (DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, getDownloadIds(cluster)); ((mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); (true); } else if (type == TYPE_COMPLETE) { final DownloadInfo info = ().next(); final Uri uri = ( .ALL_DOWNLOADS_CONTENT_URI, ); (true); final String action; if (()) { action = Constants.ACTION_LIST; } else { if ( != .DESTINATION_SYSTEMCACHE_PARTITION) { action = Constants.ACTION_OPEN; } else { action = Constants.ACTION_LIST; } } final Intent intent = new Intent(action, uri, mContext, ); (DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, getDownloadIds(cluster)); ((mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); final Intent hideIntent = new Intent(Constants.ACTION_HIDE, uri, mContext, ); ((mContext, 0, hideIntent, 0)); }
//This code is updated and downloaded Progressif (total > 0) { final int percent = (int) ((current * 100) / total); percentText = (.download_percent, percent); if (speed > 0) { final long remainingMillis = ((total - current) * 1000) / speed; remainingText = (.download_remaining, (remainingMillis)); } (100, percent, false); } else { (100, 0, true); }
Finally, call (tag, 0, notif); set the title and description of different Notifications according to different states
Thank you for reading, I hope it can help you. Thank you for your support for this site!