标签:
DownloadProvider 是Android提供的DownloadManager的增强版,亮点是支持断点下载,提供了“开始下载”,“暂停下载”,“重新下载”,“删除下载”接口。源码下载地址
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 
 | 
@Overridepublic 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方法的时序图:

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