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) {
- // 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);
- }
- 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);
- @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);
- }
- mUpdateThread = new HandlerThread(TAG + "-UpdateThread");
- mUpdateThread.start();
- mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);
- getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
- true, mObserver)
而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;
- }
- private void enqueueUpdate() {
- mUpdateHandler.removeMessages(MSG_UPDATE);
- mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();
- }
- 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;
- }
- };
- 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;
- }
- 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);
- ......
- }
至此,下载文件的流程就说完了,继续回到DownloadService的updateLocked()函数中来;重点来分析DownloadNotifier的updateWith()函数,这个方法用来更新Notification
- 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);
- }
- }
- //这段代码是根据不同的状态设置不同的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));
- }
最后调用mNotifManager.notify(tag, 0, notif); 根据不同的状态来设置不同的Notification的title和description
- //这段代码是更新下载的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);
- }