DownloadProvider 是Android提供的DownloadManager的加強版,亮點是支持斷點下載,提供了「開始下載」,「暫停下載」,「從新下載」,「刪除下載」接口。源碼下載地址html
DownloadProvider開始下載的是由DownloadManager 的 enqueue方法啓動的,啓動一個新的下載任務的時序圖
開始新的下載時候會調用DownloadManager的enqueue方法,而後再執行DownloadProvider的insert方法,將下載信 息寫入數據庫,包括下載連接地址等,而後再調用DownloadService的onCreate或者onStartCommand方法。 DownloadProvider類是很是重要的類,全部操做都跟此類有關,由於要保存下載狀態。 在分析DownloadProvider的insert方法前,先看看insert方法的源碼java
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方法的時序圖:
從這個時序圖能夠看出,這裏涉及的源碼比較多,在這沒有寫,看的時候必定要對照的源碼來看。其實在下載的時候會發生不少的異常,如網絡異常,內存卡容量不足等,因此捕獲異常很重要的,捕獲後進行相關的處理,這也是體現出考慮全面的地方。網絡