Android DownloadManager源码笔记

来源:互联网 发布:淘宝客服团队建设 编辑:程序博客网 时间:2024/06/05 06:42
  1. DownloadManger虽然也需要通过context的getSystemService来获得实例,但是其本身和ConnectivityManager之类的不太一样,其本身构造所需要的所有参数,均是当前Application可以独立提供的: new DownloadManager(ctx.getContentResolver(), ctx.getPackageName()),不需要通过ServiceManager获得一个远端的binder进行构造。
  2. 究其原因,是因为DownloadManager虽然最终也是依靠RPC进行work,但是其RPC手段是通过ContentResolver和ContentService来实现,不需要一条专门的RPC通道(binder)
  3. DownloadManger本身的职责完全是一个发报机,使用者通过其提供的Request规范将自己的请求进行对象化(不过随后可以看到,这个对象化手段也完全不是给远端使用的,只是其内部使用),然后在DownloadManager的enqueue(Request request)中,调用request本身的toContentValues(mPackageName)将Request封装的信息全部转化为ContentValues中的k-v, 进而调用mResolver.insert(Downloads.Impl.CONTENT_URI, values)将Request转化的信息以ContentResolver + insert这种方式传递给远端的DownloadProvider,进行了RPC通信.
  4. 进到DownloadProvider的insert(final Uri uri, final ContentValues values):

    • 将DownloadManager传递来的信息全部提取出来
    • 将整理后的信息插入到DB中(flying的信息需要持久化,也是为了信息共享)
    • 调用notifyContentChanged将DB的变化通告给监听该uri修改的对象(DownloadService)
    • 启动DownloadService(也可能已经在运行了)
  5. DownloadService注册了一个监听Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI(content://downloads/all_downloads)修改的ContentObserver,其被通知变化时,会调其enqueueUpdate(),而start DownloadService会触发其onStartCommand(), 同样会触发enqueueUpdate()

  6. enqueueUpdate()做的事情只是post一个(会把之前post的撤销)MSG_UPDATE的message到mUpdateHandler(对应一个Service onCreate时创建并start的一个HandlerThread), Message还携带了一个mLastStartId参数,该参数取自onStartCommand(.,.,int startId)的startId, startId唯一的标识了一次对该DownloadService的start请求

  7. mUpdateHandler注册的对Message的Callback是mUpdateCallback, 其handleMessage做了如下的操作:

    • updateLocked()把其内部维护的mDownloads信息和DownloadProvider的状态进行同步,返回是否当前还有正在运行的task
      • 如果还有正在运行的Task, enqueueFinalUpdate()
      • 否则stopSelfResult(startId)将自己结束掉(因为已经没活可干了),将注册的ContentObserver注销掉,quit之前start的mUpdateThread(清理善后).
  8. updateLocked():

    • 通过ContentResolver提取Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI的信息(就是当前Download Task的信息)到cursor中。
    • 基于Resolver和cursor来构造一个DownloadInfo.Reader.
    • 遍历Cursor的Column:
      • 如果该Column对应的DownloadTask已经在之前的Download信息(mDownloads)中存在了,那么调用updateDownload(reader, info, now)来根据DB中的最新的信息对其进行更新
      • 否则需要根据DB中新Task的信息构造一个新的DownloadTask并将其添加到mDownloads中,InsertDownloadLocked(reader, now)
      • 然后如果该下载任务没有被删除,那么调用info.startDownload**IfReady**(mExecutor)和info.startScan**IfReady**(mScanner)来将该Task进行启动。
        • startDownloadIfReady(ExecutorService executor)只有在DownloadTask (isReady && !isActive isActive的判断则是该DownloadInfo代表的Task当前正在被execute并且还没结束)时才会构造一个DownloadThread(implements Runnable)任务将其放入到executor进行调度执行。
        • DownloadThread -> run() -> runInternal() -> executeDownload()
        • executeDownload()本身可以支持断点续传(206 HTTP_PARTIAL), 一部分体现在其调用的函数addRequestHeaders()中,里面会根据上次下载的字节数来添加“Range” header.
        • 在真正的网络请求发起前,会调用checkConnectivity()来检测当前的网络状况,如果网络不可用或者不是该DownloadTask适用的场景(比如要求WIFI下载,但是当前是mobile),会结束此次网络请求并将状态修改为STATUS_WAITING_FOR_NETWORK
        • 在download的过程中,返回Data的response code(200/206)时会调用transferData进而调用reportProgress(State state)来更新ALL_DOWNLOADS_CONTENT_URI + TaskId的uri对应的在DB中的信息,同时还会调用mNotifier(DownloadNotifier).notifyDownloadSpeed(mInfo.mId, state.mSpeed)来更新下载速度的变化,该函数只改变数据,不会刷新UI。
        • transferData内部是一个for无限循环来批量的从网络的stream那里读取内容(批量读取的buffer大小是Constants.BUFFER_SIZE),每批量读取完一次, 调用checkPausedOrCanceled(state)来检查当前的情况(比如网络发生了变化)是否还允许继续读取(从而实现了对下载的控制), 如果不允许,会直接抛出异常来结束这个无限循环
        • DownloadNotifier本身的定位就是通过NotificationManager来反映出当前的下载信息。不过注意其本身真正出发UI变化的是updateWith(Collection downloads)函数,
  9. 对于网络变化的监听是在DownloadReceiver中实现的(该Receiver还承载了很多其他的监听职责),在网络发生变化并且网络可用时,会start DownloadService来触发DownloadService的enqueueUpdate()进而启动之前因为网络原因失败/暂停或者没有被开始的Task. DownloadReceiver的注册是在packages/providers/DownloadProvider/AndroidManifest.xml中标明的。

0 0