欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android DownloadProvider 源码详解

程序员文章站 2024-03-01 11:55:46
android downloadprovider 源码分析: download的源码编译分为两个部分,一个是downloadprovider.apk, 一个是downlo...

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

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