用粗糙而簡陋的語言描述完了Andriod系統的DownloadManager的DownloadThread類,那麼,繼續咱們的粗糙吧。如今就來描述一下DownloadManager的生命——DownloadService類。一如前面的思路,如今假設咱們都沒看過DownloadService的實現。咱們打算怎麼弄懂它呢,或者,對於這個神奇的類,你有哪些疑問呢。如下是我能想到的問題: java
一、它完成了什麼功能,最主要作了什麼事兒,這確定是咱們第一個關心的。 數據庫
二、系統服務與咱們所用的應用層Service有什麼不一樣嗎? app
三、對於一個Service,它實現了哪些接口? ide
四、這裏是否是就是下載的開始點呢? 學習
先看看第三個問題吧,第三個問題最簡單,爲何呢,看看源碼,看它Override 了幾個接口就知道了。 this
/** * Performs the background downloads requested by applications that use the Downloads provider. */ public class DownloadService extends Service { @Override public IBinder onBind(Intent i) { throw new UnsupportedOperationException("Cannot bind to Download Manager Service"); } @Override public void onCreate() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { } @Override public void onDestroy() { } @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { } }
從實現的接口上來講,它只是實現了一個服務最基礎的幾個接口,並無其餘特殊接口。不過,這裏有一個比較有意思的接口實現,對了,就是OnBind()。它告訴別人,你不能綁定我,只能啓動我。通常來講,若是咱們要綁定一個服務,可能會但願經過與服務註冊一個遠程回調,而後服務經過遠程的回調將計算結果傳給綁定服務的客戶端。那麼,可想而知,DownloadService是但願不與客戶端直接通訊的。這算不算與咱們寫的Service的不一樣點呢。 spa
那麼它是怎麼通訊的,簡單點說,就是經過DownloadProvider來進行的。其實,這樣作也能很容易就理解到。由於若是是採用接口的方式的話,服務可能還沒發完全部回調,結果本身掛調了,那麼就有可能它的客戶端就得不到正常的更新。從而致使狀態錯誤。 線程
接下來,能夠看第一個問題了。它都作了些什麼。從面向對象的角度來講。一個類能不能作什麼,是否是先要看看它有哪些成員屬性以及成員方法呢。那就行看其成員屬性吧。 rest
/** amount of time to wait to connect to MediaScannerService before timing out */ // 等待掃描服務的超時時間 private static final long WAIT_TIMEOUT = 10 * 1000; /** Observer to get notified when the content observer's data changes */ //可以監聽DownloadProvider的Observer private DownloadManagerContentObserver mObserver; /** Class to handle Notification Manager updates */ // 通知欄箮理 private DownloadNotifier mNotifier; //下載隊列,經過從DownloadProvider裏取出,並將每條下載記錄與其生成的下載ID做爲一個Map值。 private Map<Long, DownloadInfo> mDownloads = Maps.newHashMap(); /** * The thread that updates the internal download list from the content * provider. */ //更新線程,是DownloadService的一個內部類,同時,這個也是其功臣類,幕後的英雄。 @VisibleForTesting UpdateThread mUpdateThread; /** * Whether the internal download list should be updated from the content * provider. */ //判斷是否須要更新mDownloads private boolean mPendingUpdate; /** * The ServiceConnection object that tells us when we're connected to and disconnected from * the Media Scanner */ //媒體掃描,不關的能夠無論,目前我就沒管它 private MediaScannerConnection mMediaScannerConnection; private boolean mMediaScannerConnecting; /** * The IPC interface to the Media Scanner */ private IMediaScannerService mMediaScannerService; //外觀接口,其實就是經過這個接口去作一些公共的事 @VisibleForTesting SystemFacade mSystemFacade; //存儲管理 private StorageManager mStorageManager;
以上基本就是DoiwnloadService的成員屬性了。關於媒體掃描,暫時能夠無論,由於它並不決定下載,初步理解階段,咱們只關心咱們最關心的問題。這裏,咱們只關心最有用,關係也最緊密的兩個成員,即mDonwnloads以及mUpdateThread。 code
簡單的過了一遍其成員接口,以及其它所實現的接口。應該對其一些感性的認識了,至少不該該那麼陌生了。好了,那麼真正去分析這個類吧。
對於一個服務,最讓人容易想到的就是其生命週期。因此,引導咱們的頭,確定是其onCrreate與onStartCommand.你們都知道,onCreate只執行一次,而onStartCommand會隨服務被startService而啓動屢次。下面依次來看這兩個代碼。
/** * Initializes the service when it is first created */ @Override public void onCreate() { super.onCreate(); //初始化外觀接口 if (mSystemFacade == null) { mSystemFacade = new RealSystemFacade(this); } //實始化並註冊一個監聽DownloadProvider的Observer mObserver = new DownloadManagerContentObserver(); getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true, mObserver); //媒體掃描相關 mMediaScannerService = null; mMediaScannerConnecting = false; mMediaScannerConnection = new MediaScannerConnection(); // 通知管理 mNotifier = new DownloadNotifier(this); mNotifier.cancelAll(); //初始化存儲管理 mStorageManager = StorageManager.getInstance(getApplicationContext()); //從DownloadProvider更新下載。 updateFromProvider(); }
onCreate與咱們的習慣寫法應該差很少,就是初始化實例。固然,這裏也作了一件很重要的事情,那就是從DownloadProvider更新下載。
@Override public int onStartCommand(Intent intent, int flags, int startId) { int returnValue = super.onStartCommand(intent, flags, startId); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service onStart"); } updateFromProvider(); return returnValue; }
onStartCommand就作一件咱們所認爲的一件很重要的事兒,即更新下載列表。好吧,不防來看看這個方法的實現吧。
/** * Parses data from the content provider into private array */ private void updateFromProvider() { synchronized (this) { mPendingUpdate = true; if (mUpdateThread == null) { mUpdateThread = new UpdateThread(); mSystemFacade.startThread(mUpdateThread); } } }
哈哈,我最喜歡看這種實現了,簡單明瞭。首先判斷了這個線程是否爲NULL,這就代表了這個線程的惟一性。然再經過外觀接口啓動這個線程。關於這個外觀接口是如何啓動線程的呢,暫時無論,仍是堅持一個原則,關心咱們關心的,從流程上看懂它。
除了onCrreate與onStartCommand兩個生命週期的方法調用了updateFromProvider().還有兩個地方調用了。即監聽DownloadProvider的Observer和媒體掃描的回調接口裏。它們的代碼分別以下:
監聽DownloadProvider的Observer /** * Receives notifications when the data in the content provider changes */ private class DownloadManagerContentObserver extends ContentObserver { public DownloadManagerContentObserver() { super(new Handler()); } /** * Receives notification when the data in the observed content * provider changes. */ @Override public void onChange(final boolean selfChange) { if (Constants.LOGVV) { Log.v(Constants.TAG, "Service ContentObserver received notification"); } updateFromProvider(); } }
/** * Gets called back when the connection to the media * scanner is established or lost. */ public class MediaScannerConnection implements ServiceConnection { public void onServiceConnected(ComponentName className, IBinder service) { if (Constants.LOGVV) { Log.v(Constants.TAG, "Connected to Media Scanner"); } synchronized (DownloadService.this) { try { mMediaScannerConnecting = false; mMediaScannerService = IMediaScannerService.Stub.asInterface(service); if (mMediaScannerService != null) { updateFromProvider(); } } finally { // notify anyone waiting on successful connection to MediaService DownloadService.this.notifyAll(); } } }
四個調用的地方 ,就說明了下載列表的更新,只能是這四種方式。其實在Android中,這四種方式是基本上包括了的。這裏就說明白了第四個問題了吧,下載確實是從這裏開始的。而關於UpdateThread是如何實現的,這屬於細節問題。可看可不看,爲何。由於實現的方式千千萬萬,最重要的是下載的這一管理思想。固然,秉着學習的態度,咱們仍是要繼續看下去的。那就繼續看看這個UpdateThread類的實現吧。
private class UpdateThread extends Thread { public UpdateThread() { super("Download Service"); } @Override public void run() { //把本身設爲後臺線程 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 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; //這裏注意,啓動是一個永真循環。這個有點消息隊列的味道了吧。 for (;;) { synchronized (DownloadService.this) { if (mUpdateThread != this) { throw new IllegalStateException( "multiple UpdateThreads in DownloadService"); } //永真循環的出口。這晨要明白的是mPendingUpdate是在兩個地方被賦值,即在updateFromProvider裏被設爲true,表示準備更新或者正在更新。而在檢查完mPendingUpdate以後,其值立刻又會被置爲false。這也就是說,若是沒有觸發新的事件調用到updateFromProvider,那麼本次更新就結束了。 if (!mPendingUpdate) { mUpdateThread = null; if (!keepService) { stopSelf(); } if (wakeUp != Long.MAX_VALUE) { scheduleAlarm(wakeUp); } return; } mPendingUpdate = false; } synchronized (mDownloads) { long now = mSystemFacade.currentTimeMillis(); boolean mustScan = false; keepService = false; wakeUp = Long.MAX_VALUE; Set<Long> idsNoLongerInDatabase = new HashSet<Long>(mDownloads.keySet()); //查詢出全部的下載 Cursor cursor = getContentResolver().query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null, null, null); if (cursor == null) { continue; } try { //初始化DownloadInfo的ContentResolver和Cursor,從而可讓DownloadInfo本身從數據庫中取出數據填充到本身的屬性成員中去。這是很是符合面向對象原則的。包括後面還會看到準備下載也是DownloadInfo本身完成的。 DownloadInfo.Reader reader = new DownloadInfo.Reader(getContentResolver(), cursor); int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); if (Constants.LOGVV) { Log.i(Constants.TAG, "number of rows from downloads-db: " + cursor.getCount()); } //循環遍歷Cursor,再熟悉不過了。 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { long id = cursor.getLong(idColumn); //取出一份數據後,在idsNoLongerInDatabase這個Set裏標記下這個數據在數據庫中是存在的。 idsNoLongerInDatabase.remove(id); DownloadInfo info = mDownloads.get(id); //判斷隊列裏是否已經有這個DownloadInfo了,若是沒有則經過insertDownloadLocked方法將其插入到下載隊列,並經過DownloadInfo自身來啓動下載。若是已經在下載列表裏了,則更新其數據。 if (info != null) { updateDownload(reader, info, now); } else { info = insertDownloadLocked(reader, now); } if (info.shouldScanFile() && !scanFile(info, true, false)) { mustScan = true; keepService = true; } 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(); } //移除掉不在數據庫中,但又在下載附表裏的DownloadInfo,由於它們已經沒用了。 for (Long id : idsNoLongerInDatabase) { deleteDownloadLocked(id); } //下面的一大段都與媒體掃描相關。主要判斷某DownloadInfo是否已經被刪除了。若是被刪除了,在這裏要把它媒體庫中刪除掉,還要把它從下載數據庫中刪除,以及還要刪除其所對應的文件 // is there a need to start the DownloadService? yes, if there are rows to be // deleted. if (!mustScan) { for (DownloadInfo info : mDownloads.values()) { if (info.mDeleted && TextUtils.isEmpty(info.mMediaProviderUri)) { mustScan = true; keepService = true; break; } } } mNotifier.updateWith(mDownloads.values()); if (mustScan) { bindMediaScanner(); } else { mMediaScannerConnection.disconnectMediaScanner(); } // look for all rows with deleted flag set and delete the rows from the database // permanently for (DownloadInfo info : mDownloads.values()) { if (info.mDeleted) { // this row is to be deleted from the database. but does it have // mediaProviderUri? if (TextUtils.isEmpty(info.mMediaProviderUri)) { if (info.shouldScanFile()) { // initiate rescan of the file to - which will populate // mediaProviderUri column in this row if (!scanFile(info, false, true)) { throw new IllegalStateException("scanFile failed!"); } continue; } } else { // yes it has mediaProviderUri column already filled in. // delete it from MediaProvider database. getContentResolver().delete(Uri.parse(info.mMediaProviderUri), null, null); } // delete the file deleteFileIfExists(info.mFileName); // delete from the downloads db getContentResolver().delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, Downloads.Impl._ID + " = ? ", new String[]{String.valueOf(info.mId)}); } } } } }
到這裏,DownloadService大概是講完了的。固然不少細節地方都沒有講到。由於細節的話須要結束整個代碼來說纔有可能講的透徹些。
寫在後面的話:
關於前面的四個問題,三、4應該是沒問題了。而第2個問題,這個服務有什麼特別的,看完源碼後,會發現,其實也沒什麼特別的,不過它倒是沒有AIDL的。而第1個問題,它幹了什麼事兒。總結下來至少有如下三點:
一、接收更新,即updateFromProvider,一切都從這裏開始,不論是新增長的下載或者是其餘好比刪除、暫停等操做。注意系統下載是支持斷點續傳的,也有暫停和恢復的方法,只是它沒提供出接口出來。
二、構造一個本地下載列表。把更新獲得的數據都構形成DownloadInfo,保存在這裏列表裏,並經過DownloadInfo自身來啓動下載。(PS:下載就啓動了嗎?)
三、清理被刪除的DownloadInfo。其實這也算一個比較大的功能,由於其實一個下載涉及到了三方面的數據,媒體庫,下載數據庫,還有下載的文件。三個都是須要同步刪除的。