DownloadProvider 源码详细分析

DownloadProvider 简介
DownloadProvider 是Android提供的DownloadManager的增强版,亮点是支持断点下载,提供了“开始下载”,“暂停下载”,“重新下载”,“删除下载”接口。源码下载地址

DownloadProvider 详细分析

DownloadProvider开始下载的是由DownloadManager 的 enqueue方法启动的,启动一个新的下载任务的时序图 

开始新的下载时候会调用DownloadManager的enqueue方法,然后再执行DownloadProvider的insert方法,将下载信息写入数据库,包括下载链接地址等,然后再调用DownloadService的onCreate或者onStartCommand方法。 DownloadProvider类是非常重要的类,所有操作都跟此类有关,因为要保存下载状态。 在分析DownloadProvider的insert方法前,先看看insert方法的源码

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

@Override

public Uri insert(final Uri uri, final ContentValues values) {

    checkInsertPermissions(values);

    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    // note we disallow inserting into ALL_DOWNLOADS

    int match = sURIMatcher.match(uri);

    if (match != MY_DOWNLOADS) {

        Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: "

                + uri);

        throw new IllegalArgumentException("Unknown/Invalid URI " + uri);

    }

    ContentValues filteredValues = new ContentValues();

    ......

    Integer dest = values.getAsInteger(Downloads.COLUMN_DESTINATION);

    if (dest != null) {

    

        ......

        

    }

    Integer vis = values.getAsInteger(Downloads.COLUMN_VISIBILITY);

    if (vis == null) {

        if (dest == Downloads.DESTINATION_EXTERNAL) {

            filteredValues.put(Downloads.COLUMN_VISIBILITY,

                    Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

        } else {

            filteredValues.put(Downloads.COLUMN_VISIBILITY,

                    Downloads.VISIBILITY_HIDDEN);

        }

    } else {

        filteredValues.put(Downloads.COLUMN_VISIBILITY, vis);

    }

    ......

    String pckg = values.getAsString(Downloads.COLUMN_NOTIFICATION_PACKAGE);

    String clazz = values.getAsString(Downloads.COLUMN_NOTIFICATION_CLASS);

    if (pckg != null && (clazz != null || isPublicApi)) {

        ......

    }

    ......

    //启动下载服务

    Context context = getContext();

    context.startService(new Intent(context, DownloadService.class));

    //插入数据库

    long rowID = db.insert(DB_TABLE, null, filteredValues);

    if (rowID == -1) {

        Log.d(Constants.TAG, "couldn‘t insert into downloads database");

        return null;

    }

    insertRequestHeaders(db, rowID, values);

    //启动下载服务

    context.startService(new Intent(context, DownloadService.class));

    notifyContentChanged(uri, match);

    return ContentUris.withAppendedId(Downloads.CONTENT_URI, rowID);

}

每次开始一个新的下载任务,都会插入数据库,然后启动启动下载服务类DownloadService,所以真正处理下载的是后台服务DownloadService,DownloadService中有个下载线程类DownloadThread,DownloadService时序图 

如果DownloadService没有启动将会执行onCreate()------>onStartCommand()方法,否则执行onStartCommand()方法。然后执行updateFromProvider()方法启动UpdateThread线程,准备启动DownloadThread线程。 分析UpdateThread的run方法前先看看run方法的源码:

?


1

2

3

4

5

6

7

8

9

10

11

public void run() {

    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    //如果数据里的存储的达到了1000以上时候,将会删除status>200即失败的记录

    trimDatabase();

    removeSpuriousFiles();

    boolean keepService = false;

    // for each update from the database, remember which download is

    // supposed to get restarted soonest in the future

    long wakeUp = Long.MAX_VALUE;

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

        //会一直在此循环,直到启动完所有下载任务

    for (;;) {

        synchronized (DownloadService.this) {

            if (mUpdateThread != this) {

                throw new IllegalStateException(

                        "multiple UpdateThreads in DownloadService");

            }

            if (!mPendingUpdate) {

                mUpdateThread = null;

                if (!keepService) {

                    stopSelf();

                }

                if (wakeUp != Long.MAX_VALUE) {

                    scheduleAlarm(wakeUp);

                }

                return;

            }

            mPendingUpdate = false;

        }

        long now = mSystemFacade.currentTimeMillis();

        keepService = false;

        wakeUp = Long.MAX_VALUE;

        Set<long> idsNoLongerInDatabase = new HashSet<long>(

                mDownloads.keySet());

        Cursor cursor = getContentResolver().query(

                Downloads.ALL_DOWNLOADS_CONTENT_URI, null, null, null,

                null);

        if (cursor == null) {

            continue;

        }

        try {

            DownloadInfo.Reader reader = new DownloadInfo.Reader(

                    getContentResolver(), cursor);

            int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);

            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor

                    .moveToNext()) {

                long id = cursor.getLong(idColumn);

                idsNoLongerInDatabase.remove(id);

                DownloadInfo info = mDownloads.get(id);

                if (info != null) {

                    updateDownload(reader, info, now);

                } else {

                    info = insertDownload(reader, now);

                }

                if (info.hasCompletionNotification()) {

                    keepService = true;

                }

                long next = info.nextAction(now);

                if (next == 0) {

                    keepService = true;

                } else if (next > 0 && next < wakeUp) {

                    wakeUp = next;

                }

            }

        } finally {

            cursor.close();

        }

        for (Long id : idsNoLongerInDatabase) {

            deleteDownload(id);

        }

        // is there a need to start the DownloadService? yes, if there

        // are rows to be deleted.

        for (DownloadInfo info : mDownloads.values()) {

            if (info.mDeleted) {

                keepService = true;

                break;

            }

        }

        mNotifier.updateNotification(mDownloads.values());

        // look for all rows with deleted flag set and delete the rows

        // from the database

        // permanently

        for (DownloadInfo info : mDownloads.values()) {

            if (info.mDeleted) {

                Helpers.deleteFile(getContentResolver(), info.mId,

                        info.mFileName, info.mMimeType);

            }

        }

    }

}</long></long>

UpdateThread线程负责从数据库中获取下载任务,该线程不会每次都新建新的对象,只有UpdateThread为空时候才会新建,在此线程的run()方法中有两个for循环,外循环是从数据获取下载任务,确保插入数据库的任务都能被启动,直到启动完所有的下载任务才会退出。内循环是遍历从数据库获取的没完成或者刚开始的下载任务,启动下载。 DownloadThrea线程是真正负责下载的线程,每次启动一个任务都会创建一个新的下载线程对象(对手机来说会耗很大的CPU,所有要加上同步锁或者线程池来控制同时下载的数量),看看DownloadThread run方法的时序图: 
从这个时序图可以看出,这里涉及的源码比较多,在这没有写,看的时候一定要对照的源码来看。其实在下载的时候会发生很多的异常,如网络异常,内存卡容量不足等,所以捕获异常很重要的,捕获后进行相关的处理,这也是体现出考虑全面的地方。

时间: 2024-08-11 01:36:10

DownloadProvider 源码详细分析的相关文章

OpenStack_Swift源码分析——创建Ring及添加设备源码详细分析

1 创建Ring 代码详细分析 在OpenStack_Swift--Ring组织架构中我们详细分析了Ring的具体工作过程,下面就Ring中增加设备,删除设备,已经重新平衡的实现过程作详细的介绍. 首先看RingBuilder类 def __init__(self, part_power, replicas, min_part_hours): #why 最大 2**32 if part_power > 32: raise ValueError("part_power must be at

ConcurrentHashMap 源码详细分析(JDK1.8)

ConcurrentHashMap 源码详细分析(JDK1.8) 1. 概述 <HashMap 源码详细分析(JDK1.8)>:https://segmentfault.com/a/1190000012926722 Java7 整个 ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全.所以很

android_launcher的源码详细分析

转载请注明出处:http://blog.csdn.net/fzh0803/archive/2011/03/26/6279995.aspx 去年做了launcher相关的工作,看了很长时间.很多人都在修改launcher,但还没有详细的文档,把自己积累的东西分享出来,大家一起积累.这份源码是基于2.1的launcher2,以后版本虽有变化,但大概的原理一直还是保留了. 一.主要文件和类 1.Launcher.java:launcher中主要的activity. 2.DragLayer.java:l

RWTHLM 源码详细分析(三)

现在是第三篇,后面的顺序是从输出层,到隐层,然后到输入层的顺序来写,最后在写一下整个框架.这篇介绍输出层的实现,整个程序非常关键的是矩阵相乘的函数,所以在看整个输出层实现之前,非常有必要详细的介绍一下里面反复用到的矩阵相乘函数的各个参数的含义.先看一下FastMatrixMatrixMultiply这个函数,如下: inline void FastMatrixMatrixMultiply( const float alpha, const float a[], const bool transp

Backbone的localStorage.js源码详细分析

Backbone.localStorage.js是将数据存储到游览器客户端本地的(当没有服务器的情况下) 地址:https://github.com/jeromegn/Backbone.localStorage 1     整个函数是一个自执行函数,简化版形式如下 (function(a,fn) { //... })(a,fn); 2     9-12行/**---------此处是判断上下文有没有引入AMD模块(如requirejs)-----------*/ if(typeofdefine=

HashMap 源码详细解析 (JDK1.8)

概要 HashMap 最早出现在 JDK 1.2 中,底层基于散列算法实现.HashMap 允许 null 键和 null 值,在计算哈键的哈希值时,null 键哈希值为 0.HashMap 并不保证键值对的顺序,这意味着在进行某些操作后,键值对的顺序可能会发生变化.另外,需要注意的是,HashMap 是非线程安全类,在多线程环境下可能会存在问题. HashMap 底层是基于散列算法实现,散列算法分为散列再探测和拉链式.HashMap 则使用了拉链式的散列算法,并在 JDK 1.8 中引入了红黑

FFmpeg源码简单分析:结构体成员管理系统-AVOption

===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFmpeg源码结构图 - 编码 [通用] FFmpeg 源码简单分析:av_register_all() FFmpeg 源码简单分析:avcodec_register_all() FFmpeg 源码简单分析:内存的分配和释放(av_malloc().av_free()等) FFmpeg 源码简单分析:常

RxJava &amp;&amp; Agera 从源码简要分析基本调用流程(2)

版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qcloud.com/community 接上篇RxJava && Agera 从源码简要分析基本调用流程(1)我们从"1.订阅过程"."2.变换过程"进行分析,下篇文章我们继续分析"3.线程切换过程" 3.线程切换过程 从上文中我们知道了R

wifidog 源码初分析(4)-转

在上一篇<wifidog 源码处分析(3)>的流程结束后,接入设备的浏览器重定向至 路由器 上 wifidog 的 http 服务(端口 2060) /wifidog/auth 上(且携带了 认证服务器 为此接入设备分配的 token),本篇就是从 wifidog 接收到 /wifidog/auth 的访问后的 校验流程. - 根据<wifidog 源码初分析(2)>中描述的,在 wifidog 启动 http 服务前,注册了一个针对访问路径 /wifidog/auth 的回调,如