DownloadProvider 详细分析

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的时候,也可以对这一段进行参考:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public static void startingDownload(Activity activity,  
  2.         String url, String userAgent, String contentDisposition,  
  3.         String mimetype, String referer, boolean privateBrowsing, long contentLength,  
  4.         String filename, String downloadPath) {  
  5.     // java.net.URI is a lot stricter than KURL so we have to encode some  
  6.     // extra characters. Fix for b 2538060 and b 1634719  
  7.     WebAddress webAddress;  
  8.     try {  
  9.         webAddress = new WebAddress(url);  
  10.         webAddress.setPath(encodePath(webAddress.getPath()));  
  11.     } catch (Exception e) {  
  12.         // This only happens for very bad urls, we want to chatch the  
  13.         // exception here  
  14.         Log.e(LOGTAG, "Exception trying to parse url:" + url);  
  15.         return;  
  16.     }  
  17.   
  18.     String addressString = webAddress.toString();  
  19.     Uri uri = Uri.parse(addressString);  
  20.     final DownloadManager.Request request;  
  21.     try {  
  22.         request = new DownloadManager.Request(uri);  
  23.     } catch (IllegalArgumentException e) {  
  24.         Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();  
  25.         return;  
  26.     }  
  27.     request.setMimeType(mimetype);  
  28.     // set downloaded file destination to /sdcard/Download.  
  29.     // or, should it be set to one of several Environment.DIRECTORY* dirs  
  30.     // depending on mimetype?  
  31.     try {  
  32.         setDestinationDir(downloadPath, filename, request);  
  33.     } catch (Exception e) {  
  34.         showNoEnoughMemoryDialog(activity);  
  35.         return;  
  36.     }  
  37.     // let this downloaded file be scanned by MediaScanner - so that it can  
  38.     // show up in Gallery app, for example.  
  39.     request.allowScanningByMediaScanner();  
  40.     request.setDescription(webAddress.getHost());  
  41.     // XXX: Have to use the old url since the cookies were stored using the  
  42.     // old percent-encoded url.  
  43.     String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);  
  44.     request.addRequestHeader("cookie", cookies);  
  45.     request.addRequestHeader("User-Agent", userAgent);  
  46.     request.addRequestHeader("Referer", referer);  
  47.     request.setNotificationVisibility(  
  48.             DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);  
  49.     final DownloadManager manager = (DownloadManager) activity  
  50.             .getSystemService(Context.DOWNLOAD_SERVICE);  
  51.     new Thread("Browser download") {  
  52.         public void run() {  
  53.             manager.enqueue(request);  
  54.         }  
  55.     }.start();  
  56.     showStartDownloadToast(activity);  
  57. }  
在这个操作中,我们看到添加了request的各种参数,然后最后调用了DownloadManager的enqueue进行下载,并且在开始后,弹出了开始下载的这个toast。manager是一个DownloadManager的实例,DownloadManager是存在与 frameworks / base / core / java / android / app / DownloadManager.java。可以看到enqueue的实现为:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public long enqueue(Request request) {  
  2.     ContentValues values = request.toContentValues(mPackageName);  
  3.     Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);  
  4.     long id = Long.parseLong(downloadUri.getLastPathSegment());  
  5.    return id;  

enqueue函数主要是将Rquest实例分解组成一个ContentValues实例,并且添加到数据库中,函数返回插入的这条数据返回的ID;ContentResolver.insert函数会调用到DownloadProvider实现的ContentProvider的insert函数中去,如果我们去查看insert的code的话,我们可以看到操作是很多的。但是我们只需要关注几个关键的部分:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. ......  
  2. //将相关的请求参数,配置等插入到downloads数据库;  
  3. long rowID = db.insert(DB_TABLE, null, filteredValues);  
  4. ......  
  5. //将相关的请求参数,配置等插入到request_headers数据库中;  
  6. insertRequestHeaders(db, rowID, values);  
  7. ......  
  8. if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==  
  9.                 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {  
  10.             // When notification is requested, kick off service to process all  
  11.             // relevant downloads.  
  12. //启动DownloadService进行下载及其它工作  
  13.             if (Downloads.Impl.isNotificationToBeDisplayed(vis)) {  
  14.                 context.startService(new Intent(context, DownloadService.class));  
  15.             }  
  16.         } else {  
  17.             context.startService(new Intent(context, DownloadService.class));  
  18.         }  
  19.         notifyContentChanged(uri, match);  
  20.         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);  
在这边,我们就可以看到下载的DownloadService的调用了。因为是一个startService的方法,所以我们在DownloadService里面,是要去走oncreate的方法的。

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void onCreate() {  
  3.     super.onCreate();  
  4.     if (Constants.LOGVV) {  
  5.         Log.v(Constants.TAG, "Service onCreate");  
  6.     }  
  7.   
  8.     if (mSystemFacade == null) {  
  9.         mSystemFacade = new RealSystemFacade(this);  
  10.     }  
  11.   
  12.     mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);  
  13.     mStorageManager = new StorageManager(this);  
  14.   
  15.    mUpdateThread = new HandlerThread(TAG + "-UpdateThread");  
  16.     mUpdateThread.start();  
  17.     mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);  
  18.     mScanner = new DownloadScanner(this);  
  19.     mNotifier = new DownloadNotifier(this);  
  20.     mNotifier.cancelAll();  
  21.   
  22.     mObserver = new DownloadManagerContentObserver();  
  23.     getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,  
  24.             true, mObserver);  
  25. }  
这边的话,我们可以看到先去启动了一个handler去接收callback的处理

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. mUpdateThread = new HandlerThread(TAG + "-UpdateThread");  
  2.  mUpdateThread.start();  
  3.  mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);  
然后去

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,  
  2.                 true, mObserver)  
是去注册监听Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的Observer。

而oncreate之后,就会去调用onStartCommand方法.

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. ublic int onStartCommand(Intent intent, int flags, int startId) {  
  3.     int returnValue = super.onStartCommand(intent, flags, startId);  
  4.     if (Constants.LOGVV) {  
  5.         Log.v(Constants.TAG, "Service onStart");  
  6.     }  
  7.     mLastStartId = startId;  
  8.     enqueueUpdate();  
  9.     return returnValue;  
  10. }  
在enqueueUpdate的函数中,我们会向mUpdateHandler发送一个MSG_UPDATE Message,

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. private void enqueueUpdate() {  
  2.     mUpdateHandler.removeMessages(MSG_UPDATE);  
  3.     mUpdateHandler.obtainMessage(MSG_UPDATE, mLastStartId, -1).sendToTarget();  
  4. }  
mUpdateCallback中接收到并且处理:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. private Handler.Callback mUpdateCallback = new Handler.Callback() {  
  2.         @Override  
  3.         public boolean handleMessage(Message msg) {  
  4.             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  5.             final int startId = msg.arg1;  
  6.             final boolean isActive;  
  7.             synchronized (mDownloads) {  
  8.                 isActive = updateLocked();  
  9.             }  
  10.             ......  
  11.             if (isActive) {  
  12. //如果Active,则会在Delayed 5×60000ms后发送MSG_FINAL_UPDATE Message,主要是为了“any finished operations that didn't trigger an update pass.”  
  13.                 enqueueFinalUpdate();  
  14.             } else {  
  15. //如果没有Active的任务正在进行,就会停止Service以及其它  
  16.                 if (stopSelfResult(startId)) {  
  17.                     if (DEBUG_LIFECYCLE) Log.v(TAG, "Nothing left; stopped");  
  18.                     getContentResolver().unregisterContentObserver(mObserver);  
  19.                     mScanner.shutdown();  
  20.                     mUpdateThread.quit();  
  21.                 }  
  22.             }  
  23.             return true;  
  24.         }  
  25.     };  
这边的 重点是updateLocked()函数

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1.     private boolean updateLocked() {  
  2.         final long now = mSystemFacade.currentTimeMillis();  
  3.   
  4.         boolean isActive = false;  
  5.         long nextActionMillis = Long.MAX_VALUE;  
  6. //mDownloads初始化是一个空的Map<Long, DownloadInfo>  
  7.         final Set<Long> staleIds = Sets.newHashSet(mDownloads.keySet());  
  8.   
  9.         final ContentResolver resolver = getContentResolver();  
  10. //获取所有的DOWNLOADS任务  
  11.         final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,  
  12.                 nullnullnullnull);  
  13.         try {  
  14.             final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);  
  15.             final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID);  
  16. //迭代Download Cusor  
  17.             while (cursor.moveToNext()) {  
  18.                 final long id = cursor.getLong(idColumn);  
  19.                 staleIds.remove(id);  
  20.   
  21.                 DownloadInfo info = mDownloads.get(id);  
  22. //开始时,mDownloads是没有任何内容的,info==null  
  23.                 if (info != null) {  
  24. //从数据库更新最新的Download info信息,来监听数据库的改变并且反应到界面上  
  25.                     updateDownload(reader, info, now);  
  26.                 } else {  
  27. //添加新下载的Dwonload info到mDownloads,并且从数据库读取新的Dwonload info  
  28.                     info = insertDownloadLocked(reader, now);  
  29.                 }  
  30. //这里的mDeleted参数表示的是当我删除了正在或者已经下载的内容时,首先数据库会update这个info.mDeleted为true,而不是直接删除文件  
  31.                 if (info.mDeleted) {  
  32. //不详细解释delete函数,主要是删除数据库内容和现在文件内容  
  33.                     if (!TextUtils.isEmpty(info.mMediaProviderUri)) {  
  34.                 resolver.delete(Uri.parse(info.mMediaProviderUri), nullnull);  
  35.                     }  
  36.                     deleteFileIfExists(info.mFileName);  
  37.                     resolver.delete(info.getAllDownloadsUri(), nullnull);  
  38.   
  39.                 } else {  
  40.                     // 开始下载文件  
  41.                     final boolean activeDownload = info.startDownloadIfReady(mExecutor);  
  42.   
  43.                     // 开始media scanner  
  44.                     final boolean activeScan = info.startScanIfReady(mScanner);  
  45.                     isActive |= activeDownload;  
  46.                     isActive |= activeScan;  
  47.                 }  
  48.   
  49.                 // Keep track of nearest next action  
  50.                 nextActionMillis = Math.min(info.nextActionMillis(now), nextActionMillis);  
  51.             }  
  52.         } finally {  
  53.             cursor.close();  
  54.         }  
  55.         // Clean up stale downloads that disappeared  
  56.         for (Long id : staleIds) {  
  57.             deleteDownloadLocked(id);  
  58.         }  
  59.         // Update notifications visible to user  
  60.         mNotifier.updateWith(mDownloads.values());  
  61.         if (nextActionMillis > 0 && nextActionMillis < Long.MAX_VALUE) {  
  62.             final Intent intent = new Intent(Constants.ACTION_RETRY);  
  63.             intent.setClass(this, DownloadReceiver.class);  
  64.             mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + nextActionMillis,  
  65.                     PendingIntent.getBroadcast(this0, intent, PendingIntent.FLAG_ONE_SHOT));  
  66.         }  
  67.         return isActive;  
  68.     }  
重点来看看文件的下载,startDownloadIfReady函数:

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1.  public boolean startDownloadIfReady(ExecutorService executor) {  
  2.         synchronized (this) {  
  3.             final boolean isReady = isReadyToDownload();  
  4.             final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();  
  5.             if (isReady && !isActive) {  
  6. //更新数据库的任务状态为STATUS_RUNNING  
  7.                 if (mStatus != Impl.STATUS_RUNNING) {  
  8.                     mStatus = Impl.STATUS_RUNNING;  
  9.                     ContentValues values = new ContentValues();  
  10.                     values.put(Impl.COLUMN_STATUS, mStatus);  
  11.                     mContext.getContentResolver().update(getAllDownloadsUri(), values, nullnull);  
  12.                 }  
  13. //开始下载任务  
  14.                 mTask = new DownloadThread(  
  15.                         mContext, mSystemFacade, this, mStorageManager, mNotifier);  
  16.                 mSubmittedTask = executor.submit(mTask);  
  17.             }  
  18.             return isReady;  
  19.         }  
  20.     }  

在DownloadThread的处理中,如果HTTP的状态是ok的话,会去进行transferDate的处理。
  
  
  1. private void transferData(State state, HttpURLConnection conn) throws StopRequestException {  
  2. ......  
  3. in = conn.getInputStream();  
  4. ......  
  5. //获取InputStream和OutPutStream  
  6. if (DownloadDrmHelper.isDrmConvertNeeded(state.mMimeType)) {  
  7.                     drmClient = new DrmManagerClient(mContext);  
  8.                     final RandomAccessFile file = new RandomAccessFile(  
  9.                             new File(state.mFilename), "rw");  
  10.                     out = new DrmOutputStream(drmClient, file, state.mMimeType);  
  11.                     outFd = file.getFD();  
  12.                 } else {  
  13.                     out = new FileOutputStream(state.mFilename, true);  
  14.                     outFd = ((FileOutputStream) out).getFD();  
  15.                 }  
  16. ......  
  17. // Start streaming data, periodically watch for pause/cancel  
  18.             // commands and checking disk space as needed.  
  19.             transferData(state, in, out);  
  20. ......  
  21. }  
------
  
  
  1. private void transferData(State state, InputStream in, OutputStream out)  
  2.             throws StopRequestException {  
  3.         final byte data[] = new byte[Constants.BUFFER_SIZE];  
  4.         for (;;) {  
  5. //从InputStream中读取内容信息,“in.read(data)”,并且对数据库中文件下载大小进行更新  
  6.             int bytesRead = readFromResponse(state, data, in);  
  7.             if (bytesRead == -1) { // success, end of stream already reached  
  8.                 handleEndOfStream(state);  
  9.                 return;  
  10.             }  
  11.             state.mGotData = true;  
  12. //利用OutPutStream写入读取的InputStream,"out.write(data, 0, bytesRead)"  
  13.             writeDataToDestination(state, data, bytesRead, out);  
  14.             state.mCurrentBytes += bytesRead;  
  15.             reportProgress(state);  
  16.             }  
  17.             checkPausedOrCanceled(state);  
  18.         }  
  19.     }  
至此,下载文件的流程就说完了,继续回到DownloadService的updateLocked()函数中来;重点来分析DownloadNotifier的updateWith()函数,这个方法用来更新Notification
  
  
  1. //这段代码是根据不同的状态设置不同的Notification的icon  
  2.  if (type == TYPE_ACTIVE) {  
  3.                 builder.setSmallIcon(android.R.drawable.stat_sys_download);  
  4.             } else if (type == TYPE_WAITING) {  
  5.                 builder.setSmallIcon(android.R.drawable.stat_sys_warning);  
  6.             } else if (type == TYPE_COMPLETE) {  
  7.                 builder.setSmallIcon(android.R.drawable.stat_sys_download_done);  
  8.             }  
  1. //这段代码是根据不同的状态来设置不同的notification Intent  
  2. // Build action intents  
  3.             if (type == TYPE_ACTIVE || type == TYPE_WAITING) {  
  4.                 // build a synthetic uri for intent identification purposes  
  5.                 final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build();  
  6.                 final Intent intent = new Intent(Constants.ACTION_LIST,  
  7.                         uri, mContext, DownloadReceiver.class);  
  8.                 intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,  
  9.                         getDownloadIds(cluster));  
  10.                 builder.setContentIntent(PendingIntent.getBroadcast(mContext,  
  11.                         0, intent, PendingIntent.FLAG_UPDATE_CURRENT));  
  12.                 builder.setOngoing(true);  
  13.   
  14.             } else if (type == TYPE_COMPLETE) {  
  15.                 final DownloadInfo info = cluster.iterator().next();  
  16.                 final Uri uri = ContentUris.withAppendedId(  
  17.                         Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, info.mId);  
  18.                 builder.setAutoCancel(true);  
  19.   
  20.                 final String action;  
  21.                 if (Downloads.Impl.isStatusError(info.mStatus)) {  
  22.                     action = Constants.ACTION_LIST;  
  23.                 } else {  
  24.                     if (info.mDestination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) {  
  25.                         action = Constants.ACTION_OPEN;  
  26.                     } else {  
  27.                         action = Constants.ACTION_LIST;  
  28.                     }  
  29.                 }  
  30.   
  31.                 final Intent intent = new Intent(action, uri, mContext, DownloadReceiver.class);  
  32.                 intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,  
  33.                         getDownloadIds(cluster));  
  34.                 builder.setContentIntent(PendingIntent.getBroadcast(mContext,  
  35.                         0, intent, PendingIntent.FLAG_UPDATE_CURRENT));  
  36.   
  37.                 final Intent hideIntent = new Intent(Constants.ACTION_HIDE,  
  38.                         uri, mContext, DownloadReceiver.class);  
  39.                 builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0));  
  40.             }  
  1. //这段代码是更新下载的Progress  
  2. if (total > 0) {  
  3.                     final int percent = (int) ((current * 100) / total);  
  4.                     percentText = res.getString(R.string.download_percent, percent);  
  5.   
  6.                     if (speed > 0) {  
  7.                         final long remainingMillis = ((total - current) * 1000) / speed;  
  8.                         remainingText = res.getString(R.string.download_remaining,  
  9.                                 DateUtils.formatDuration(remainingMillis));  
  10.                     }  
  11.   
  12.                     builder.setProgress(100, percent, false);  
  13.                 } else {  
  14.                     builder.setProgress(1000true);  
  15.                 }  
最后调用mNotifManager.notify(tag, 0, notif);根据不同的状态来设置不同的Notification的title和description
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值