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方法的时序图:

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

Android DownloadProvider学习的更多相关文章

  1. Android DownloadProvider学习 (二)

    DownloadManager.Request用来请求一个下载,DownloadManager.Query用来查询下载信息,这两个类的具体功能会在后面穿插介绍.DownloadManager的源码可见 ...

  2. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  3. Android开发学习路线图

    Android开发学习方法: Android是一个比较庞大的体系,从底层的Linux内核到上层的应用层,各部分的内容跨度也比较大.因此,一个好的学习方法对我们学习Android开发很重要. 在此建议, ...

  4. Android动画学习(二)——Tween Animation

    前两天写过一篇Android动画学习的概述,大致的划分了下Android Animation的主要分类,没有看过的同学请移步:Android动画学习(一)——Android动画系统框架简介.今天接着来 ...

  5. Android自动化学习笔记:编写MonkeyRunner脚本的几种方式

    ---------------------------------------------------------------------------------------------------- ...

  6. Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例

    ---------------------------------------------------------------------------------------------------- ...

  7. android开发学习笔记000

    使用书籍:<疯狂android讲义>——李刚著,2011年7月出版 虽然现在已2014,可我挑来跳去,还是以这本书开始我的android之旅吧. “疯狂源自梦想,技术成就辉煌.” 让我这个 ...

  8. Android Animation学习(六) View Animation介绍

    Android Animation学习(六) View Animation介绍 View Animation View animation系统可以用来执行View上的Tween animation和F ...

  9. Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition

    Android Animation学习(五) ApiDemos解析:容器布局动画 LayoutTransition Property animation系统还提供了对ViewGroup中的View改变 ...

随机推荐

  1. [原创]WPF应用MediaPlayer播放声音断续、不全解决方案

    1.检查扬声器和驱动程序. 测试方法:首先,应用Windows Media Player播放器播放,看是否有问题,如果有问题,基本断定是驱动程序问题.其次,点击扬声器,选择测试,查看扬声器是否好用,如 ...

  2. 2013年度最强AngularJS资源合集

    原文在这里 2013年度最强AngularJS资源合集 来看我的视频教程,国内第一款完整的AngularJS视频教程http://www.imooc.com/learn/156 司徒正美 写道 htt ...

  3. Yes, Virginia, Scala is hard

    首先要说的是,我是一个Scala粉丝,我作为一个Scala语言的倡导者差不多有5年历史了.我写了不少Scala语言方面的书和文章.我曾在数十个公司里做过Scala和Lift框架项目的开发.我对很多的S ...

  4. 为iOS的mobileconfig文件进行签名

    本文主要讲,使用苹果开发者证书进行签名达到目的,本文两种方法,一种是刀耕火种的一步步操作,一种使用脚本签名 一.导出苹果证书进行签名 1.从钥匙串(keychain)中导出证书 实用工具->钥匙 ...

  5. HDU 1712 分组背包

    ACboy needs your help Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Ot ...

  6. SpringMVC 数据转换 & 数据格式化 & 数据校验

    数据绑定流程 1. Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象 ...

  7. Bootstrap 模态框在用户点击背景空白处时会自动关闭

    问题: Bootstrap 模态框在用户点击背景空白处时,会自动关闭. 解决方法: 在HTML页面中编写模态框时,在div初始化时添加属性 aria-hidden=”true” data-backdr ...

  8. SQL SERVER 字符串常用函数

    在开发T-SQL时,经常会需要对字符串进行各种各样的操作,下面介绍常用的字符串函数. 1.获取字符的ASCII码 ASCII ASCII码是对字符的标准编码.要获取字符的ASCII码就可以通过调用AS ...

  9. 也来说说C/C++里的volatile关键字

    去年年底的样子,何登成写了一篇关于C/C++ volatile关键字的深度剖析blog(C/C++ Volatile关键词深度剖析).全文深入分析了volatile关键字的三个特性.这里不想就已有内容 ...

  10. cookie 保存上次访问url方法

    if (Session[Enums.UserInfoSeesion] == null) { HttpCookie cookie = Request.Cookies[Enums.UserLastAcce ...