当前位置: 移动技术网 > IT编程>移动开发>Android > Android DownloadProvider 源码详解

Android DownloadProvider 源码详解

2019年07月24日  | 移动技术网IT编程  | 我要评论

雷迪嘎嘎图片,背影家园旭东的日志,官员因反腐冷落企业

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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网