Android 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-10-15 02:27:43

Android DownloadProvider学习的相关文章

java/android 设计模式学习笔记(14)---外观模式

这篇博客来介绍外观模式(Facade Pattern),外观模式也称为门面模式,它在开发过程中运用频率非常高,尤其是第三方 SDK 基本很大概率都会使用外观模式.通过一个外观类使得整个子系统只有一个统一的高层的接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节.当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块.ImageLoader 模块等.其实我们在开发过程中可能已经使用过很多次外观模式,只是没有从理论层面去了解它. 转载请注明出处:http://bl

java/android 设计模式学习笔记(10)---建造者模式

这篇博客我们来介绍一下建造者模式(Builder Pattern),建造者模式又被称为生成器模式,是创造性模式之一,与工厂方法模式和抽象工厂模式不同,后两者的目的是为了实现多态性,而 Builder 模式的目的则是为了将对象的构建与展示分离.Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程.一个复杂的对象有大量的组成部分,比如汽车它有车轮.方向盘.发动机.以及各种各样的小零件,要将这些部件装配成一辆汽车,这个装配过

java/android 设计模式学习笔记(一)---单例模式

前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使用的时候也会有一些坑. PS:对技术感兴趣的同鞋加群544645972一起交流 设计模式总目录 java/android 设计模式学习笔记目录 特点 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式的使用很广泛,比如:线程池(threadpool).缓存(cache).对

Android NDK学习笔记(一) 为什么要用NDK?

NDK是什么 NDK是Native Development Kit的简称,即本地开发工具包.通过NDK,Android允许开发人员使用本地代码语言(例如C/C++)来完成应用的部分(甚至全部)功能.注意:由于翻译原因,有些地方也把Native翻译为"原生". NDK是SDK的一个补充,可以帮助你做这些事情: 生成可以在ARM CPU,Android 1.5(及以上)平台运行的JNI兼容的共享库. 将生成的共享库放置在应用程序项目路径的合适位置,使其能自动地添加进你最终的(和经过签名的)

Android:日常学习笔记(8)———探究UI开发(5)

Android:日常学习笔记(8)---探究UI开发(5) ListView控件的使用 ListView的简单用法 public class MainActivity extends AppCompatActivity { private String[] data={"Apple","Banana","Orange","Watermelon","Pear","Grape","

java/android 设计模式学习笔记(7)---装饰者模式

这篇将会介绍装饰者模式(Decorator Pattern),装饰者模式也称为包装模式(Wrapper Pattern),结构型模式之一,其使用一种对客户端透明的方式来动态的扩展对象的功能,同时它也是继承关系的一种替代方案之一,但比继承更加灵活.在现实生活中也可以看到很多装饰者模式的例子,或者可以大胆的说装饰者模式无处不在,就拿一件东西来说,可以给它披上无数层不一样的外壳,但是这件东西还是这件东西,外壳不过是用来扩展这个东西的功能而已,这就是装饰者模式,装饰者的这个角色也许各不相同但是被装饰的对

【转】Android开发学习总结(一)——搭建最新版本的Android开发环境

最近由于工作中要负责开发一款Android的App,之前都是做JavaWeb的开发,Android开发虽然有所了解,但是一直没有搭建开发环境去学习,Android的更新速度比较快了,Android1.0是2008年发布的,截止到目前为止Android已经更新Android5.0.1,学习Android开发的第一步就是搭建Android的开发环境,博客园里面有不少人也写了关于如何搭建Android开发环境的文章,我也看了一下,但是感觉都比较旧了,对照着做不一定能够搭建成功,但是有些搭建步骤是还是可

Android开发学习之路--网络编程之xml、json

一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载xampp,然后安装之类的就不再多讲了,参考http://cnbin.github.io/blog/2015/06/05/mac-an-zhuang-he-shi-yong-xampp/.安装好后,启动xampp,之后在浏览器输入localhost或者127.0.0.1就可以看到如下所示了: 这个就

Android UI学习 - ListView (android.R.layout.simple_list_item_1是个什么东西)

Android UI学习 - ListView 2010-06-20 18:21:35 标签:Android UI 移动开发 ListView ListActivity 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://android.blog.51cto.com/268543/336162 ListActivity ListActivity是一个专门显示ListView的Activity类,它内置了ListView对象,只要我