Android DownloadProvider 源码详解
Android DownloadProvider 源码分析:
Download的源码编译分为两个部分,一个是DownloadProvider.apk, 一个是DownloadProviderUi.apk.
这两个apk的源码分别位于
packages/providers/DownloadProvider/ui/class="lazy" data-src
packages/providers/DownloadProvider/class="lazy" data-src
其中,DownloadProvider的部分是下载逻辑的实现,而DownloadProviderUi是界面部分的实现。
然后DownloadProvider里面的下载虽然主要是通过DownloadService进行的操作,但是由于涉及到Notification的更新,下载进度的展示,下载的管理等。
所以还是有不少其它的类来分别进行操作。
DownloadProvider -- 数据库操作的封装,继承自ContentProvider;
DownloadManager -- 大部分逻辑是进一步封装数据操作,供外部调用;
DownloadService -- 封装文件download,delete等操作,并且操纵下载的norification;继承自Service;
DownloadNotifier -- 状态栏Notification逻辑;
DownloadReceiver -- 配合DownloadNotifier进行文件的操作及其Notification;
DownloadList -- Download app主界面,文件界面交互;
下载一般是从Browser里面点击链接开始,我们先来看一下Browser中的代码
在browser的class="lazy" data-src/com/Android/browser/DownloadHandler.Java函数中,我们可以看到一个很完整的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的各种参数,然后最后调用了DownloadManager的enqueue进行下载,并且在开始后,弹出了开始下载的这个toast。manager是一个DownloadManager的实例,DownloadManager是存在与frameworks/base/core/java/android/app/DownloadManager.java。可以看到enqueue的实现为:
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函数主要是将Rquest实例分解组成一个ContentValues实例,并且添加到数据库中,函数返回插入的这条数据返回的ID;ContentResolver.insert函数会调用到DownloadProvider实现的ContentProvider的insert函数中去,如果我们去查看insert的code的话,我们可以看到操作是很多的。但是我们只需要关注几个关键的部分:
......
//将相关的请求参数,配置等插入到downloads数据库;
long rowID = db.insert(DB_TABLE, null, filteredValues);
......
//将相关的请求参数,配置等插入到request_headers数据库中;
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)
是去注册监听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的函数中,我们会向mUpdateHandler发送一个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后发送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()函数
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) {
//从数据库更新最新的Download info信息,来监听数据库的改变并且反应到界面上
updateDownload(reader, info, now);
} else {
//添加新下载的Dwonload info到mDownloads,并且从数据库读取新的Dwonload info
info = insertDownloadLocked(reader, now);
}
//这里的mDeleted参数表示的是当我删除了正在或者已经下载的内容时,首先数据库会update这个info.mDeleted为true,而不是直接删除文件
if (info.mDeleted) {
//不详细解释delete函数,主要是删除数据库内容和现在文件内容
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函数:
public boolean startDownloadIfReady(ExecutorService executor) {
synchronized (this) {
final boolean isReady = isReadyToDownload();
final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
if (isReady && !isActive) {
//更新数据库的任务状态为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的状态是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)”,并且对数据库中文件下载大小进行更新
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);
}
}
至此,下载文件的流程就说完了,继续回到DownloadService的updateLocked()函数中来;重点来分析DownloadNotifier的updateWith()函数,这个方法用来更新Notification
//这段代码是根据不同的状态设置不同的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);
}
//这段代码是根据不同的状态来设置不同的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);根据不同的状态来设置不同的Notification的title和description
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
您可能感兴趣的文章:Android应用开发SharedPreferences存储数据的使用方法android TextView设置中文字体加粗实现方法Android 动画之TranslateAnimation应用详解android PopupWindow 和 Activity弹出窗口实现方式android压力测试命令monkey详解解决Android SDK下载和更新失败的方法详解android客户端从服务器端获取json数据并解析的实现代码android listview优化几种写法详细介绍android调试工具DDMS的使用详解
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341