Android DownloadProvider學習

DownloadProvider 簡介

DownloadProvider 是Android提供的DownloadManager的加強版,亮點是支持斷點下載,提供了「開始下載」,「暫停下載」,「從新下載」,「刪除下載」接口。源碼下載地址html

DownloadProvider 詳細分析

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方法的時序圖:
從這個時序圖能夠看出,這裏涉及的源碼比較多,在這沒有寫,看的時候必定要對照的源碼來看。其實在下載的時候會發生不少的異常,如網絡異常,內存卡容量不足等,因此捕獲異常很重要的,捕獲後進行相關的處理,這也是體現出考慮全面的地方。網絡

相關文章
相關標籤/搜索