用粗糙而简陋的语言描述完了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。其实这也算一个比较大的功能,由于其实一个下载涉及到了三方面的数据,媒体库,下载数据库,还有下载的文件。三个都是须要同步删除的。