Android DownloadProvider 源码详解
AndroidDownloadProvider源码分析:
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--Downloadapp主界面,文件界面交互;
下载一般是从Browser里面点击链接开始,我们先来看一下Browser中的代码
在browser的src/com/Android/browser/DownloadHandler.Java函数中,我们可以看到一个很完整的Download的调用,我们在写自己的app的时候,也可以对这一段进行参考:
publicstaticvoidstartingDownload(Activityactivity, Stringurl,StringuserAgent,StringcontentDisposition, Stringmimetype,Stringreferer,booleanprivateBrowsing,longcontentLength, Stringfilename,StringdownloadPath){ //java.net.URIisalotstricterthanKURLsowehavetoencodesome //extracharacters.Fixforb2538060andb1634719 WebAddresswebAddress; try{ webAddress=newWebAddress(url); webAddress.setPath(encodePath(webAddress.getPath())); }catch(Exceptione){ //Thisonlyhappensforverybadurls,wewanttochatchthe //exceptionhere Log.e(LOGTAG,"Exceptiontryingtoparseurl:"+url); return; } StringaddressString=webAddress.toString(); Uriuri=Uri.parse(addressString); finalDownloadManager.Requestrequest; try{ request=newDownloadManager.Request(uri); }catch(IllegalArgumentExceptione){ Toast.makeText(activity,R.string.cannot_download,Toast.LENGTH_SHORT).show(); return; } request.setMimeType(mimetype); //setdownloadedfiledestinationto/sdcard/Download. //or,shoulditbesettooneofseveralEnvironment.DIRECTORY*dirs //dependingonmimetype? try{ setDestinationDir(downloadPath,filename,request); }catch(Exceptione){ showNoEnoughMemoryDialog(activity); return; } //letthisdownloadedfilebescannedbyMediaScanner-sothatitcan //showupinGalleryapp,forexample. request.allowScanningByMediaScanner(); request.setDescription(webAddress.getHost()); //XXX:Havetousetheoldurlsincethecookieswerestoredusingthe //oldpercent-encodedurl. Stringcookies=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); finalDownloadManagermanager=(DownloadManager)activity .getSystemService(Context.DOWNLOAD_SERVICE); newThread("Browserdownload"){ publicvoidrun(){ manager.enqueue(request); } }.start(); showStartDownloadToast(activity); }
在这个操作中,我们看到添加了request的各种参数,然后最后调用了DownloadManager的enqueue进行下载,并且在开始后,弹出了开始下载的这个toast。manager是一个DownloadManager的实例,DownloadManager是存在与frameworks/base/core/java/android/app/DownloadManager.java。可以看到enqueue的实现为:
publiclongenqueue(Requestrequest){ ContentValuesvalues=request.toContentValues(mPackageName); UridownloadUri=mResolver.insert(Downloads.Impl.CONTENT_URI,values); longid=Long.parseLong(downloadUri.getLastPathSegment()); returnid;
enqueue函数主要是将Rquest实例分解组成一个ContentValues实例,并且添加到数据库中,函数返回插入的这条数据返回的ID;ContentResolver.insert函数会调用到DownloadProvider实现的ContentProvider的insert函数中去,如果我们去查看insert的code的话,我们可以看到操作是很多的。但是我们只需要关注几个关键的部分:
...... //将相关的请求参数,配置等插入到downloads数据库; longrowID=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){ //Whennotificationisrequested,kickoffservicetoprocessall //relevantdownloads. //启动DownloadService进行下载及其它工作 if(Downloads.Impl.isNotificationToBeDisplayed(vis)){ context.startService(newIntent(context,DownloadService.class)); } }else{ context.startService(newIntent(context,DownloadService.class)); } notifyContentChanged(uri,match); returnContentUris.withAppendedId(Downloads.Impl.CONTENT_URI,rowID);
在这边,我们就可以看到下载的DownloadService的调用了。因为是一个startService的方法,所以我们在DownloadService里面,是要去走oncreate的方法的。
@Override publicvoidonCreate(){ super.onCreate(); if(Constants.LOGVV){ Log.v(Constants.TAG,"ServiceonCreate"); } if(mSystemFacade==null){ mSystemFacade=newRealSystemFacade(this); } mAlarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE); mStorageManager=newStorageManager(this); mUpdateThread=newHandlerThread(TAG+"-UpdateThread"); mUpdateThread.start(); mUpdateHandler=newHandler(mUpdateThread.getLooper(),mUpdateCallback); mScanner=newDownloadScanner(this); mNotifier=newDownloadNotifier(this); mNotifier.cancelAll(); mObserver=newDownloadManagerContentObserver(); getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true,mObserver); }
这边的话,我们可以看到先去启动了一个handler去接收callback的处理
mUpdateThread=newHandlerThread(TAG+"-UpdateThread"); mUpdateThread.start(); mUpdateHandler=newHandler(mUpdateThread.getLooper(),mUpdateCallback);
然后去
getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true,mObserver)
是去注册监听Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的Observer。
而oncreate之后,就会去调用onStartCommand方法.
@Override ublicintonStartCommand(Intentintent,intflags,intstartId){ intreturnValue=super.onStartCommand(intent,flags,startId); if(Constants.LOGVV){ Log.v(Constants.TAG,"ServiceonStart"); } mLastStartId=startId; enqueueUpdate(); returnreturnValue; }
在enqueueUpdate的函数中,我们会向mUpdateHandler发送一个MSG_UPDATEMessage,
privatevoidenqueueUpdate(){ mUpdateHandler.removeMessages(MSG_UPDATE); mUpdateHandler.obtainMessage(MSG_UPDATE,mLastStartId,-1).sendToTarget(); }
mUpdateCallback中接收到并且处理:
privateHandler.CallbackmUpdateCallback=newHandler.Callback(){ @Override publicbooleanhandleMessage(Messagemsg){ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); finalintstartId=msg.arg1; finalbooleanisActive; synchronized(mDownloads){ isActive=updateLocked(); } ...... if(isActive){ //如果Active,则会在Delayed5×60000ms后发送MSG_FINAL_UPDATEMessage,主要是为了“anyfinishedoperationsthatdidn'ttriggeranupdatepass.” enqueueFinalUpdate(); }else{ //如果没有Active的任务正在进行,就会停止Service以及其它 if(stopSelfResult(startId)){ if(DEBUG_LIFECYCLE)Log.v(TAG,"Nothingleft;stopped"); getContentResolver().unregisterContentObserver(mObserver); mScanner.shutdown(); mUpdateThread.quit(); } } returntrue; } };
这边的重点是updateLocked()函数
privatebooleanupdateLocked(){ finallongnow=mSystemFacade.currentTimeMillis(); booleanisActive=false; longnextActionMillis=Long.MAX_VALUE; //mDownloads初始化是一个空的Map<Long,DownloadInfo> finalSet<Long>staleIds=Sets.newHashSet(mDownloads.keySet()); finalContentResolverresolver=getContentResolver(); //获取所有的DOWNLOADS任务 finalCursorcursor=resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null,null,null,null); try{ finalDownloadInfo.Readerreader=newDownloadInfo.Reader(resolver,cursor); finalintidColumn=cursor.getColumnIndexOrThrow(Downloads.Impl._ID); //迭代DownloadCusor while(cursor.moveToNext()){ finallongid=cursor.getLong(idColumn); staleIds.remove(id); DownloadInfoinfo=mDownloads.get(id); //开始时,mDownloads是没有任何内容的,info==null if(info!=null){ //从数据库更新最新的Downloadinfo信息,来监听数据库的改变并且反应到界面上 updateDownload(reader,info,now); }else{ //添加新下载的Dwonloadinfo到mDownloads,并且从数据库读取新的Dwonloadinfo 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{ //开始下载文件 finalbooleanactiveDownload=info.startDownloadIfReady(mExecutor); //开始mediascanner finalbooleanactiveScan=info.startScanIfReady(mScanner); isActive|=activeDownload; isActive|=activeScan; } //Keeptrackofnearestnextaction nextActionMillis=Math.min(info.nextActionMillis(now),nextActionMillis); } }finally{ cursor.close(); } //Cleanupstaledownloadsthatdisappeared for(Longid:staleIds){ deleteDownloadLocked(id); } //Updatenotificationsvisibletouser mNotifier.updateWith(mDownloads.values()); if(nextActionMillis>0&&nextActionMillis<Long.MAX_VALUE){ finalIntentintent=newIntent(Constants.ACTION_RETRY); intent.setClass(this,DownloadReceiver.class); mAlarmManager.set(AlarmManager.RTC_WAKEUP,now+nextActionMillis, PendingIntent.getBroadcast(this,0,intent,PendingIntent.FLAG_ONE_SHOT)); } returnisActive; }
重点来看看文件的下载,startDownloadIfReady函数:
publicbooleanstartDownloadIfReady(ExecutorServiceexecutor){ synchronized(this){ finalbooleanisReady=isReadyToDownload(); finalbooleanisActive=mSubmittedTask!=null&&!mSubmittedTask.isDone(); if(isReady&&!isActive){ //更新数据库的任务状态为STATUS_RUNNING if(mStatus!=Impl.STATUS_RUNNING){ mStatus=Impl.STATUS_RUNNING; ContentValuesvalues=newContentValues(); values.put(Impl.COLUMN_STATUS,mStatus); mContext.getContentResolver().update(getAllDownloadsUri(),values,null,null); } //开始下载任务 mTask=newDownloadThread( mContext,mSystemFacade,this,mStorageManager,mNotifier); mSubmittedTask=executor.submit(mTask); } returnisReady; } }
在DownloadThread的处理中,如果HTTP的状态是ok的话,会去进行transferDate的处理。
privatevoidtransferData(Statestate,HttpURLConnectionconn)throwsStopRequestException{ ...... in=conn.getInputStream(); ...... //获取InputStream和OutPutStream if(DownloadDrmHelper.isDrmConvertNeeded(state.mMimeType)){ drmClient=newDrmManagerClient(mContext); finalRandomAccessFilefile=newRandomAccessFile( newFile(state.mFilename),"rw"); out=newDrmOutputStream(drmClient,file,state.mMimeType); outFd=file.getFD(); }else{ out=newFileOutputStream(state.mFilename,true); outFd=((FileOutputStream)out).getFD(); } ...... //Startstreamingdata,periodicallywatchforpause/cancel //commandsandcheckingdiskspaceasneeded. transferData(state,in,out); ...... }
------
privatevoidtransferData(Statestate,InputStreamin,OutputStreamout) throwsStopRequestException{ finalbytedata[]=newbyte[Constants.BUFFER_SIZE]; for(;;){ //从InputStream中读取内容信息,“in.read(data)”,并且对数据库中文件下载大小进行更新 intbytesRead=readFromResponse(state,data,in); if(bytesRead==-1){//success,endofstreamalreadyreached 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); }elseif(type==TYPE_WAITING){ builder.setSmallIcon(android.R.drawable.stat_sys_warning); }elseif(type==TYPE_COMPLETE){ builder.setSmallIcon(android.R.drawable.stat_sys_download_done); }
//这段代码是根据不同的状态来设置不同的notificationIntent //Buildactionintents if(type==TYPE_ACTIVE||type==TYPE_WAITING){ //buildasyntheticuriforintentidentificationpurposes finalUriuri=newUri.Builder().scheme("active-dl").appendPath(tag).build(); finalIntentintent=newIntent(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); }elseif(type==TYPE_COMPLETE){ finalDownloadInfoinfo=cluster.iterator().next(); finalUriuri=ContentUris.withAppendedId( Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,info.mId); builder.setAutoCancel(true); finalStringaction; 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; } } finalIntentintent=newIntent(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)); finalIntenthideIntent=newIntent(Constants.ACTION_HIDE, uri,mContext,DownloadReceiver.class); builder.setDeleteIntent(PendingIntent.getBroadcast(mContext,0,hideIntent,0)); }
//这段代码是更新下载的Progress if(total>0){ finalintpercent=(int)((current*100)/total); percentText=res.getString(R.string.download_percent,percent); if(speed>0){ finallongremainingMillis=((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
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!