Android DownloadProvider 源码详解

Android DownloadProvider 源码详解,第1张

概述AndroidDownloadProvider源码分析:Download的源码编译分为两个部分,一个是DownloadProvider.apk,一个是DownloadProviderUi.apk.

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

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进行下载,并且在开始后,d出了开始下载的这个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,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);     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);           }           deletefileIfExists(info.mfilename);           resolver.delete(info.getAllDownloadsUri(),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,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);         } //开始下载任务         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,bytesRead)"       writeDataToDestination(state,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,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,PendingIntent.FLAG_UPDATE_CURRENT));          final Intent hIDeIntent = new Intent(Constants.ACTION_HIDE,DownloadReceiver.class);         builder.setDeleteIntent(PendingIntent.getbroadcast(mContext,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,true);         } 

最后调用mNotifManager.notify(tag,notif);根据不同的状态来设置不同的Notification的Title和description

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

总结

以上是内存溢出为你收集整理的Android DownloadProvider 源码详解全部内容,希望文章能够帮你解决Android DownloadProvider 源码详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1147752.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存