Android DownloadProvider 源码详解
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进行下载,并且在开始后,弹出了开始下载的这个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, true, 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, 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; }
重点来看看文件的下载,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, 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, 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); } 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)); }
//这段代码是更新下载的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); }
最后调用mnotifmanager.notify(tag, 0, notif);根据不同的状态来设置不同的notification的title和description
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!