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方法的时序图: 
从这个时序图可以看出,这里涉及的源码比较多,在这没有写,看的时候一定要对照的源码来看。其实在下载的时候会发生很多的异常,如网络异常,内存卡容量不足等,所以捕获异常很重要的,捕获后进行相关的处理,这也是体现出考虑全面的地方。

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

  1. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

  2. HashMap 源码详细分析(JDK1.8)

    一.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...

  3. ArrayList 源码详细分析

    1.概述 ArrayList 是一种变长的集合类,基于定长数组实现.ArrayList 允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一个 ...

  4. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

  5. android_launcher的源码详细分析

    转载请注明出处:http://blog.csdn.net/fzh0803/archive/2011/03/26/6279995.aspx 去年做了launcher相关的工作,看了很长时间.很多人都在修 ...

  6. 源码分析 ucosii/source 任务源码详细分析

    分析源码: 得先学会读文档, 函数前边的 note :是了解该程序员的思想的途径.不得不重视 代码前边的  Notes,了解思想后,然后在分析代码时看他是如何具体实现的. 1. ucosii/sour ...

  7. SpringMvc demo示例及源码详细分析

    三层架构介绍 我们的开发架构一般都是基于两种形式,一种C/S架构,也就是客户端/服务器,另一种是B/S架构,也就是浏览器/服务器.在JavaEE开发中,几乎全部都是基于B/S架构的开发.那么在B/S架 ...

  8. Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

    开心一刻 吃完晚饭,坐在院子里和父亲聊天 父亲:你有什么人生追求? 我:金钱和美女 父亲对着我的头就是一丁弓,说道:小小年纪,怎么这么庸俗,重说一次 我:事业与爱情 父亲赞赏的摸了我的头,说道:嗯嗯, ...

  9. dede源码详细分析之--全局变量覆盖漏洞的防御

    http://blog.csdn.net/ebw123/article/details/8100594

随机推荐

  1. HAProxy 日志输出及配置

    正所谓,没有软件敢说没有bug,人无完人,software is  not perfect software.是软件就可能存在bug,那么如果出现bug,我们就要分析对我们业务的影响及可能如何避免bu ...

  2. slurm.conf系统初始配置

    #slurm集群配置 ##集群名称 ClusterName=myslurm ##主控制器的主机名 ControlMachine=node11 ##主控制器的IP地址 ControlAddr=192.1 ...

  3. 阿里云centos内docker的搭建

    由于docker在17之后的版本分成了docker EE(企业版)和docker CE(社区版),那么我们在安装的时候就要开始纠结的选择了,这里我推荐了docker CE(社区版). 实际上这两个版本 ...

  4. Sql_连接查询中on筛选与where筛选的区别

    sql中的连接查询分为3种, cross join,inner join,和outer join ,  在 cross join和inner join中,筛选条件放在on后面还是where后面是没区别 ...

  5. 牛客多校第三场-A-PACM Team-多维背包的01变种

    题目我就不贴了...说不定被查到要GG... 题意就是我们需要在P,A,C,M四个属性的限制下,找到符合条件的最优解... 这样我们就需要按照0/1背包的思路,建立一个五维度数组dp[i][j][k] ...

  6. sixsix团队M2阶段Postmortem

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 要解决的问题:目前外卖网站比较多,用户很难快速找到合适的外卖,我们集合各个网站的外卖信息,为用户提 ...

  7. NEWBE CRALWER 产品需求文档

    1.产品概述 本产品是学霸软件系统的爬虫部分,由NEWBE团队负责.主要任务是从网上爬取出相关数据后提供给C705组使用. 2.产品的发展经历 2.1 产品的发展经历 本产品从2014.10.29开始 ...

  8. Linux 第八周实验 进程的切换和系统的一般执行过程

    姬梦馨 原创作品 <Linux内核分析>MOOC课程:http://mooc.study.163.com/course/USTC-1000029000 第八讲 进程的切换和系统的一般执行过 ...

  9. Mooc总结——Linux内核分析

    朱荟潼+ 原创作品转载请注明出处 :<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 学习笔记链接汇总 第 ...

  10. Practice2 结对子之“小学四则运算”

    开发环境:Eclipse,js,css,html 程序完成的方向: 1.可以出表达式里含有负整数(负整数最小不小于-100)的题目,且负数需要带括号,用户输入的结果不用带括号.如: 2*(-4) = ...