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
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!