Download的源码编译分为两个部分,一个是DownloadProvider.apk, 一个是DownloadProviderUi.apk.
这两个apk的源码分别位于
packages/providers/DownloadProvider/ui/src
packages/providers/DownloadProvider/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的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) {
-
-
- WebAddress webAddress;
- try {
- webAddress = new WebAddress(url);
- webAddress.setPath(encodePath(webAddress.getPath()));
- } catch (Exception e) {
-
-
- 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);
-
-
-
- try {
- setDestinationDir(downloadPath, filename, request);
- } catch (Exception e) {
- showNoEnoughMemoryDialog(activity);
- return;
- }
-
-
- request.allowScanningByMediaScanner();
- request.setDescription(webAddress.getHost());
-
-
- 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的话,我们可以看到操作是很多的。但是我们只需要关注几个关键的部分:
- ......
-
- long rowID = db.insert(DB_TABLE, null, filteredValues);
- ......
-
- insertRequestHeaders(db, rowID, values);
- ......
- if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
- Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
-
-
-
- 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) {
-
- enqueueFinalUpdate();
- } else {
-
- 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;
-
- 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 {
- 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);
-
- if (info != null) {
-
- updateDownload(reader, info, now);
- } else {
-
- info = insertDownloadLocked(reader, now);
- }
-
- if (info.mDeleted) {
-
- 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);
-
-
- final boolean activeScan = info.startScanIfReady(mScanner);
- isActive |= activeDownload;
- isActive |= activeScan;
- }
-
-
- nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);
- }
- } finally {
- cursor.close();
- }
-
- for (Long id : staleIds) {
- deleteDownloadLocked(id);
- }
-
- 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) {
-
- 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();
- ......
-
- 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();
- }
- ......
-
-
- transferData(state, in, out);
- ......
- }
------
- private void transferData(State state, InputStream in, OutputStream out)
- throws StopRequestException {
- final byte data[] = new byte[Constants.BUFFER_SIZE];
- for (;;) {
-
- int bytesRead = readFromResponse(state, data, in);
- if (bytesRead == -1) {
- handleEndOfStream(state);
- return;
- }
- state.mGotData = true;
-
- writeDataToDestination(state, data, bytesRead, out);
- state.mCurrentBytes += bytesRead;
- reportProgress(state);
- }
- checkPausedOrCanceled(state);
- }
- }
至此,下载文件的流程就说完了,继续回到DownloadService的updateLocked()函数中来;重点来分析DownloadNotifier的updateWith()函数,这个方法用来更新Notification
-
- 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);
- }
-
-
- if (type == TYPE_ACTIVE || type == TYPE_WAITING) {
-
- 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));
- }
-
- 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