Android DownloadProvider 源碼詳解
Android DownloadProvider 源碼分析:
Download的源碼編譯分為兩個(gè)部分,一個(gè)是DownloadProvider.apk, 一個(gè)是DownloadProviderUi.apk.
這兩個(gè)apk的源碼分別位于
packages/providers/DownloadProvider/ui/src
packages/providers/DownloadProvider/src
其中,DownloadProvider的部分是下載邏輯的實(shí)現(xiàn),而DownloadProviderUi是界面部分的實(shí)現(xiàn)。
然后DownloadProvider里面的下載雖然主要是通過(guò)DownloadService進(jìn)行的操作,但是由于涉及到Notification的更新,下載進(jìn)度的展示,下載的管理等。
所以還是有不少其它的類(lèi)來(lái)分別進(jìn)行操作。
DownloadProvider -- 數(shù)據(jù)庫(kù)操作的封裝,繼承自ContentProvider;
DownloadManager -- 大部分邏輯是進(jìn)一步封裝數(shù)據(jù)操作,供外部調(diào)用;
DownloadService -- 封裝文件download,delete等操作,并且操縱下載的norification;繼承自Service;
DownloadNotifier -- 狀態(tài)欄Notification邏輯;
DownloadReceiver -- 配合DownloadNotifier進(jìn)行文件的操作及其N(xiāo)otification;
DownloadList -- Download app主界面,文件界面交互;
下載一般是從Browser里面點(diǎn)擊鏈接開(kāi)始,我們先來(lái)看一下Browser中的代碼
在browser的src/com/Android/browser/DownloadHandler.Java函數(shù)中,我們可以看到一個(gè)很完整的Download的調(diào)用,我們?cè)趯?xiě)自己的app的時(shí)候,也可以對(duì)這一段進(jìn)行參考:
public static void startingDownload(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, String referer, boolean privateBrowsing, long contentLength, String filename, String downloadPath) { // java.net.URI 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); webAddress.setPath(encodePath(webAddress.getPath())); } catch (Exception e) { // This only happens for very bad urls, we want to chatch the // exception here Log.e(LOGTAG, "Exception trying to parse url:" + url); return; } String addressString = webAddress.toString(); Uri uri = Uri.parse(addressString); final DownloadManager.Request request; try { request = new DownloadManager.Request(uri); } catch (IllegalArgumentException e) { Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show(); return; } request.setMimeType(mimetype); // set downloaded file destination to /sdcard/Download. // or, should it be set to one of several Environment.DIRECTORY* 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. request.allowScanningByMediaScanner(); request.setDescription(webAddress.getHost()); // XXX: Have to use the old url since the cookies were stored using the // old percent-encoded url. String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing); request.addRequestHeader("cookie", cookies); request.addRequestHeader("User-Agent", userAgent); request.addRequestHeader("Referer", referer); request.setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); final DownloadManager manager = (DownloadManager) activity .getSystemService(Context.DOWNLOAD_SERVICE); new Thread("Browser download") { public void run() { manager.enqueue(request); } }.start(); showStartDownloadToast(activity); }
在這個(gè)操作中,我們看到添加了request的各種參數(shù),然后最后調(diào)用了DownloadManager的enqueue進(jìn)行下載,并且在開(kāi)始后,彈出了開(kāi)始下載的這個(gè)toast。manager是一個(gè)DownloadManager的實(shí)例,DownloadManager是存在與frameworks/base/core/java/android/app/DownloadManager.java??梢钥吹絜nqueue的實(shí)現(xiàn)為:
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;
enqueue函數(shù)主要是將Rquest實(shí)例分解組成一個(gè)ContentValues實(shí)例,并且添加到數(shù)據(jù)庫(kù)中,函數(shù)返回插入的這條數(shù)據(jù)返回的ID;ContentResolver.insert函數(shù)會(huì)調(diào)用到DownloadProvider實(shí)現(xiàn)的ContentProvider的insert函數(shù)中去,如果我們?nèi)ゲ榭磇nsert的code的話(huà),我們可以看到操作是很多的。但是我們只需要關(guān)注幾個(gè)關(guān)鍵的部分:
...... //將相關(guān)的請(qǐng)求參數(shù),配置等插入到downloads數(shù)據(jù)庫(kù); long rowID = db.insert(DB_TABLE, null, filteredValues); ...... //將相關(guān)的請(qǐng)求參數(shù),配置等插入到request_headers數(shù)據(jù)庫(kù)中; insertRequestHeaders(db, rowID, values); ...... if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { // When notification is requested, kick off service to process all // relevant downloads. //啟動(dòng)DownloadService進(jìn)行下載及其它工作 if (Downloads.Impl.isNotificationToBeDisplayed(vis)) { context.startService(new Intent(context, DownloadService.class)); } } else { context.startService(new Intent(context, DownloadService.class)); } notifyContentChanged(uri, match); return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
在這邊,我們就可以看到下載的DownloadService的調(diào)用了。因?yàn)槭且粋€(gè)startService的方法,所以我們?cè)贒ownloadService里面,是要去走oncreate的方法的。
@Override public void onCreate() { super.onCreate(); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service onCreate"); } if (mSystemFacade == null) { mSystemFacade = new RealSystemFacade(this); } mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mStorageManager = new StorageManager(this); mUpdateThread = new HandlerThread(TAG + "-UpdateThread"); mUpdateThread.start(); mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback); mScanner = new DownloadScanner(this); mNotifier = new DownloadNotifier(this); mNotifier.cancelAll(); mObserver = new DownloadManagerContentObserver(); getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true, mObserver); }
這邊的話(huà),我們可以看到先去啟動(dòng)了一個(gè)handler去接收callback的處理
mUpdateThread = new HandlerThread(TAG + "-UpdateThread"); mUpdateThread.start(); mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);
然后去
getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true, mObserver)
是去注冊(cè)監(jiān)聽(tīng)Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的Observer。
而oncreate之后,就會(huì)去調(diào)用onStartCommand方法.
@Override ublic 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的函數(shù)中,我們會(huì)向mUpdateHandler發(fā)送一個(gè)MSG_UPDATE Message,
private void enqueueUpdate() { mUpdateHandler.removeMessages(MSG_UPDATE); mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget(); }
mUpdateCallback中接收到并且處理:
private Handler.Callback mUpdateCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final int startId = msg.arg1; final boolean isActive; synchronized (mDownloads) { isActive = updateLocked(); } ...... if (isActive) { //如果Active,則會(huì)在Delayed 5×60000ms后發(fā)送MSG_FINAL_UPDATE Message,主要是為了“any finished operations that didn't trigger an update pass.” enqueueFinalUpdate(); } else { //如果沒(méi)有Active的任務(wù)正在進(jìn)行,就會(huì)停止Service以及其它 if (stopSelfResult(startId)) { if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped"); getContentResolver().unregisterContentObserver(mObserver); mScanner.shutdown(); mUpdateThread.quit(); } } return true; } };
這邊的重點(diǎn)是updateLocked()函數(shù)
private boolean updateLocked() { final long now = mSystemFacade.currentTimeMillis(); boolean isActive = false; long nextActionMillis = Long.MAX_VALUE; //mDownloads初始化是一個(gè)空的Map<Long, DownloadInfo> final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet()); final ContentResolver resolver = getContentResolver(); //獲取所有的DOWNLOADS任務(wù) final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null, null, null); try { final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor); final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); //迭代Download Cusor while (cursor.moveToNext()) { final long id = cursor.getLong(idColumn); staleIds.remove(id); DownloadInfo info = mDownloads.get(id); //開(kāi)始時(shí),mDownloads是沒(méi)有任何內(nèi)容的,info==null if (info != null) { //從數(shù)據(jù)庫(kù)更新最新的Download info信息,來(lái)監(jiān)聽(tīng)數(shù)據(jù)庫(kù)的改變并且反應(yīng)到界面上 updateDownload(reader, info, now); } else { //添加新下載的Dwonload info到mDownloads,并且從數(shù)據(jù)庫(kù)讀取新的Dwonload info info = insertDownloadLocked(reader, now); } //這里的mDeleted參數(shù)表示的是當(dāng)我刪除了正在或者已經(jīng)下載的內(nèi)容時(shí),首先數(shù)據(jù)庫(kù)會(huì)update這個(gè)info.mDeleted為true,而不是直接刪除文件 if (info.mDeleted) { //不詳細(xì)解釋delete函數(shù),主要是刪除數(shù)據(jù)庫(kù)內(nèi)容和現(xiàn)在文件內(nèi)容 if (!TextUtils.isEmpty(info.mMediaProviderUri)) { resolver.delete(Uri.parse(info.mMediaProviderUri), null, null); } deleteFileIfExists(info.mFileName); resolver.delete(info.getAllDownloadsUri(), null, null); } else { // 開(kāi)始下載文件 final boolean activeDownload = info.startDownloadIfReady(mExecutor); // 開(kāi)始media scanner final boolean activeScan = info.startScanIfReady(mScanner); 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()); if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) { 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; }
重點(diǎn)來(lái)看看文件的下載,startDownloadIfReady函數(shù):
public boolean startDownloadIfReady(ExecutorService executor) { synchronized (this) { final boolean isReady = isReadyToDownload(); final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone(); if (isReady && !isActive) { //更新數(shù)據(jù)庫(kù)的任務(wù)狀態(tài)為STATUS_RUNNING 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); } //開(kāi)始下載任務(wù) mTask = new DownloadThread( mContext, mSystemFacade, this, mStorageManager, mNotifier); mSubmittedTask = executor.submit(mTask); } return isReady; } }
在DownloadThread的處理中,如果HTTP的狀態(tài)是ok的話(huà),會(huì)去進(jìn)行transferDate的處理。
private void transferData(State state, HttpURLConnection conn) throws StopRequestException { ...... in = conn.getInputStream(); ...... //獲取InputStream和OutPutStream if (DownloadDrmHelper.isDrmConvertNeeded(state.mMimeType)) { drmClient = new DrmManagerClient(mContext); final RandomAccessFile file = new RandomAccessFile( new File(state.mFilename), "rw"); out = new DrmOutputStream(drmClient, file, state.mMimeType); outFd = file.getFD(); } else { out = new FileOutputStream(state.mFilename, 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 (;;) { //從InputStream中讀取內(nèi)容信息,“in.read(data)”,并且對(duì)數(shù)據(jù)庫(kù)中文件下載大小進(jìn)行更新 int bytesRead = readFromResponse(state, data, in); if (bytesRead == -1) { // success, end of stream already reached handleEndOfStream(state); return; } state.mGotData = true; //利用OutPutStream寫(xiě)入讀取的InputStream,"out.write(data, 0, bytesRead)" writeDataToDestination(state, data, bytesRead, out); state.mCurrentBytes += bytesRead; reportProgress(state); } checkPausedOrCanceled(state); } }
至此,下載文件的流程就說(shuō)完了,繼續(xù)回到DownloadService的updateLocked()函數(shù)中來(lái);重點(diǎn)來(lái)分析DownloadNotifier的updateWith()函數(shù),這個(gè)方法用來(lái)更新Notification
//這段代碼是根據(jù)不同的狀態(tài)設(shè)置不同的Notification的icon if (type == TYPE_ACTIVE) { builder.setSmallIcon(android.R.drawable.stat_sys_download); } else if (type == TYPE_WAITING) { builder.setSmallIcon(android.R.drawable.stat_sys_warning); } else if (type == TYPE_COMPLETE) { builder.setSmallIcon(android.R.drawable.stat_sys_download_done); }
//這段代碼是根據(jù)不同的狀態(tài)來(lái)設(shè)置不同的notification Intent // Build action intents if (type == TYPE_ACTIVE || type == TYPE_WAITING) { // build a synthetic uri for intent identification purposes final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build(); final Intent intent = new Intent(Constants.ACTION_LIST, uri, mContext, DownloadReceiver.class); intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, getDownloadIds(cluster)); builder.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); builder.setOngoing(true); } else if (type == TYPE_COMPLETE) { final DownloadInfo info = cluster.iterator().next(); final Uri uri = ContentUris.withAppendedId( Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, info.mId); builder.setAutoCancel(true); final String action; if (Downloads.Impl.isStatusError(info.mStatus)) { action = Constants.ACTION_LIST; } else { if (info.mDestination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { action = Constants.ACTION_OPEN; } else { action = Constants.ACTION_LIST; } } final Intent intent = new Intent(action, uri, mContext, DownloadReceiver.class); intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, getDownloadIds(cluster)); builder.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); final Intent hideIntent = new Intent(Constants.ACTION_HIDE, uri, mContext, DownloadReceiver.class); builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0)); }
//這段代碼是更新下載的Progress if (total > 0) { final int percent = (int) ((current * 100) / total); percentText = res.getString(R.string.download_percent, percent); if (speed > 0) { final long remainingMillis = ((total - current) * 1000) / speed; remainingText = res.getString(R.string.download_remaining, DateUtils.formatDuration(remainingMillis)); } builder.setProgress(100, percent, false); } else { builder.setProgress(100, 0, true); }
最后調(diào)用mNotifManager.notify(tag, 0, notif);根據(jù)不同的狀態(tài)來(lái)設(shè)置不同的Notification的title和description
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Android應(yīng)用開(kāi)發(fā)SharedPreferences存儲(chǔ)數(shù)據(jù)的使用方法
- android TextView設(shè)置中文字體加粗實(shí)現(xiàn)方法
- Android 動(dòng)畫(huà)之TranslateAnimation應(yīng)用詳解
- android PopupWindow 和 Activity彈出窗口實(shí)現(xiàn)方式
- android壓力測(cè)試命令monkey詳解
- 解決Android SDK下載和更新失敗的方法詳解
- android客戶(hù)端從服務(wù)器端獲取json數(shù)據(jù)并解析的實(shí)現(xiàn)代碼
- android listview優(yōu)化幾種寫(xiě)法詳細(xì)介紹
- android調(diào)試工具DDMS的使用詳解
相關(guān)文章
Android編程實(shí)現(xiàn)播放視頻時(shí)切換全屏并隱藏狀態(tài)欄的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)播放視頻時(shí)切換全屏并隱藏狀態(tài)欄的方法,結(jié)合實(shí)例形式分析了Android視頻播放事件響應(yīng)及相關(guān)屬性設(shè)置操作技巧,需要的朋友可以參考下2017-08-08Android應(yīng)用隱私合規(guī)檢測(cè)實(shí)現(xiàn)方案詳解
這篇文章主要介紹了Android應(yīng)用隱私合規(guī)檢測(cè)實(shí)現(xiàn)方案,我們需要做的就是提前檢測(cè)好自己的應(yīng)用是否存在隱私合規(guī)問(wèn)題,及時(shí)整改過(guò)來(lái),下面提供Xposed Hook思路去檢測(cè)隱私合規(guī)問(wèn)題,建議有Xposed基礎(chǔ)的童鞋閱讀,需要的朋友可以參考下2022-07-07android自定義組件實(shí)現(xiàn)儀表計(jì)數(shù)盤(pán)
這篇文章主要為大家詳細(xì)介紹了android自定義組件實(shí)現(xiàn)儀表計(jì)數(shù)盤(pán),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android?組件化神器之Arouter依賴(lài)配置使用
這篇文章主要為大家介紹了Android?組件化神器之Arouter依賴(lài)配置使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06解析ADT-20問(wèn)題 android support library
本篇文章是對(duì)ADT-20問(wèn)題 android support library進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06