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