Android DownloadProvider 源碼詳解
Android DownloadProvider 源碼分析:
Download的源碼編譯分為兩個部分,一個是DownloadProvider.apk, 一個是DownloadProviderUi.apk.
這兩個apk的源碼分別位于
packages/providers/DownloadProvider/ui/src
packages/providers/DownloadProvider/src
其中,DownloadProvider的部分是下載邏輯的實現(xiàn),而DownloadProviderUi是界面部分的實現(xiàn)。
然后DownloadProvider里面的下載雖然主要是通過DownloadService進行的操作,但是由于涉及到Notification的更新,下載進度的展示,下載的管理等。
所以還是有不少其它的類來分別進行操作。
DownloadProvider -- 數(shù)據庫操作的封裝,繼承自ContentProvider;
DownloadManager -- 大部分邏輯是進一步封裝數(shù)據操作,供外部調用;
DownloadService -- 封裝文件download,delete等操作,并且操縱下載的norification;繼承自Service;
DownloadNotifier -- 狀態(tài)欄Notification邏輯;
DownloadReceiver -- 配合DownloadNotifier進行文件的操作及其Notification;
DownloadList -- Download app主界面,文件界面交互;
下載一般是從Browser里面點擊鏈接開始,我們先來看一下Browser中的代碼
在browser的src/com/Android/browser/DownloadHandler.Java函數(shù)中,我們可以看到一個很完整的Download的調用,我們在寫自己的app的時候,也可以對這一段進行參考:
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);
}
在這個操作中,我們看到添加了request的各種參數(shù),然后最后調用了DownloadManager的enqueue進行下載,并且在開始后,彈出了開始下載的這個toast。manager是一個DownloadManager的實例,DownloadManager是存在與frameworks/base/core/java/android/app/DownloadManager.java??梢钥吹絜nqueue的實現(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實例分解組成一個ContentValues實例,并且添加到數(shù)據庫中,函數(shù)返回插入的這條數(shù)據返回的ID;ContentResolver.insert函數(shù)會調用到DownloadProvider實現(xiàn)的ContentProvider的insert函數(shù)中去,如果我們去查看insert的code的話,我們可以看到操作是很多的。但是我們只需要關注幾個關鍵的部分:
......
//將相關的請求參數(shù),配置等插入到downloads數(shù)據庫;
long rowID = db.insert(DB_TABLE, null, filteredValues);
......
//將相關的請求參數(shù),配置等插入到request_headers數(shù)據庫中;
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.
//啟動DownloadService進行下載及其它工作
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的調用了。因為是一個startService的方法,所以我們在DownloadService里面,是要去走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);
}
這邊的話,我們可以看到先去啟動了一個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)
是去注冊監(jiān)聽Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的Observer。
而oncreate之后,就會去調用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ù)中,我們會向mUpdateHandler發(fā)送一個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,則會在Delayed 5×60000ms后發(fā)送MSG_FINAL_UPDATE Message,主要是為了“any finished operations that didn't trigger an update pass.”
enqueueFinalUpdate();
} else {
//如果沒有Active的任務正在進行,就會停止Service以及其它
if (stopSelfResult(startId)) {
if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");
getContentResolver().unregisterContentObserver(mObserver);
mScanner.shutdown();
mUpdateThread.quit();
}
}
return true;
}
};
這邊的重點是updateLocked()函數(shù)
private boolean updateLocked() {
final long now = mSystemFacade.currentTimeMillis();
boolean isActive = false;
long nextActionMillis = Long.MAX_VALUE;
//mDownloads初始化是一個空的Map<Long, DownloadInfo>
final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());
final ContentResolver resolver = getContentResolver();
//獲取所有的DOWNLOADS任務
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);
//開始時,mDownloads是沒有任何內容的,info==null
if (info != null) {
//從數(shù)據庫更新最新的Download info信息,來監(jiān)聽數(shù)據庫的改變并且反應到界面上
updateDownload(reader, info, now);
} else {
//添加新下載的Dwonload info到mDownloads,并且從數(shù)據庫讀取新的Dwonload info
info = insertDownloadLocked(reader, now);
}
//這里的mDeleted參數(shù)表示的是當我刪除了正在或者已經下載的內容時,首先數(shù)據庫會update這個info.mDeleted為true,而不是直接刪除文件
if (info.mDeleted) {
//不詳細解釋delete函數(shù),主要是刪除數(shù)據庫內容和現(xiàn)在文件內容
if (!TextUtils.isEmpty(info.mMediaProviderUri)) {
resolver.delete(Uri.parse(info.mMediaProviderUri), null, null);
}
deleteFileIfExists(info.mFileName);
resolver.delete(info.getAllDownloadsUri(), null, null);
} else {
// 開始下載文件
final boolean activeDownload = info.startDownloadIfReady(mExecutor);
// 開始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;
}
重點來看看文件的下載,startDownloadIfReady函數(shù):
public boolean startDownloadIfReady(ExecutorService executor) {
synchronized (this) {
final boolean isReady = isReadyToDownload();
final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
if (isReady && !isActive) {
//更新數(shù)據庫的任務狀態(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);
}
//開始下載任務
mTask = new DownloadThread(
mContext, mSystemFacade, this, mStorageManager, mNotifier);
mSubmittedTask = executor.submit(mTask);
}
return isReady;
}
}
在DownloadThread的處理中,如果HTTP的狀態(tài)是ok的話,會去進行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中讀取內容信息,“in.read(data)”,并且對數(shù)據庫中文件下載大小進行更新
int bytesRead = readFromResponse(state, data, in);
if (bytesRead == -1) { // success, end of stream already reached
handleEndOfStream(state);
return;
}
state.mGotData = true;
//利用OutPutStream寫入讀取的InputStream,"out.write(data, 0, bytesRead)"
writeDataToDestination(state, data, bytesRead, out);
state.mCurrentBytes += bytesRead;
reportProgress(state);
}
checkPausedOrCanceled(state);
}
}
至此,下載文件的流程就說完了,繼續(xù)回到DownloadService的updateLocked()函數(shù)中來;重點來分析DownloadNotifier的updateWith()函數(shù),這個方法用來更新Notification
//這段代碼是根據不同的狀態(tài)設置不同的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);
}
//這段代碼是根據不同的狀態(tài)來設置不同的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);
}
最后調用mNotifManager.notify(tag, 0, notif);根據不同的狀態(tài)來設置不同的Notification的title和description
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關文章
Android編程實現(xiàn)播放視頻時切換全屏并隱藏狀態(tài)欄的方法
這篇文章主要介紹了Android編程實現(xiàn)播放視頻時切換全屏并隱藏狀態(tài)欄的方法,結合實例形式分析了Android視頻播放事件響應及相關屬性設置操作技巧,需要的朋友可以參考下2017-08-08
Android應用隱私合規(guī)檢測實現(xiàn)方案詳解
這篇文章主要介紹了Android應用隱私合規(guī)檢測實現(xiàn)方案,我們需要做的就是提前檢測好自己的應用是否存在隱私合規(guī)問題,及時整改過來,下面提供Xposed Hook思路去檢測隱私合規(guī)問題,建議有Xposed基礎的童鞋閱讀,需要的朋友可以參考下2022-07-07
android自定義組件實現(xiàn)儀表計數(shù)盤
這篇文章主要為大家詳細介紹了android自定義組件實現(xiàn)儀表計數(shù)盤,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11
解析ADT-20問題 android support library
本篇文章是對ADT-20問題 android support library進行了詳細的分析介紹,需要的朋友參考下2013-06-06

