DownloadManager之DownloadService浅析

用粗糙而简陋的语言描述完了Andriod系统的DownloadManager的DownloadThread类,那么,继续我们的粗糙吧。现在就来描述一下DownloadManager的生命——DownloadService类。一如前面的思路,现在假设我们都没看过DownloadService的实现。我们打算怎么弄懂它呢,或者,对于这个神奇的类,你有哪些疑问呢。以下是我能想到的问题:

1、它完成了什么功能,最主要做了什么事儿,这肯定是我们第一个关心的。

2、系统服务与我们所用的应用层Service有什么不同吗?

3、对于一个Service,它实现了哪些接口?

4、这里是不是就是下载的开始点呢?

先看看第三个问题吧,第三个问题最简单,为什么呢,看看源码,看它Override 了几个接口就知道了。

/**
 * 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的不同点呢。

那么它是怎么通信的,简单点说,就是通过DownloadProvider来进行的。其实,这样做也能很容易就理解到。因为如果是采用接口的方式的话,服务可能还没发完所有回调,结果自己挂调了,那么就有可能它的客户端就得不到正常的更新。从而导致状态错误。

接下来,可以看第一个问题了。它都做了些什么。从面向对象的角度来说。一个类能不能做什么,是不是先要看看它有哪些成员属性以及成员方法呢。那就行看其成员属性吧。

 /** 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。

    简单的过了一遍其成员接口,以及其它所实现的接口。应该对其一些感性的认识了,至少不应该那么陌生了。好了,那么真正去分析这个类吧。

    对于一个服务,最让人容易想到的就是其生命周期。所以,引导我们的头,肯定是其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大概是讲完了的。当然很多细节地方都没有讲到。因为细节的话需要结束整个代码来讲才有可能讲的透彻些。


写在后面的话:

    关于前面的四个问题,3、4应该是没问题了。而第2个问题,这个服务有什么特别的,看完源码后,会发现,其实也没什么特别的,不过它却是没有AIDL的。而第1个问题,它干了什么事儿。总结下来至少有以下三点:

1、接收更新,即updateFromProvider,一切都从这里开始,不管是新增加的下载或者是其他比如删除、暂停等操作。注意系统下载是支持断点续传的,也有暂停和恢复的方法,只是它没提供出接口出来。

2、构造一个本地下载列表。把更新得到的数据都构造成DownloadInfo,保存在这里列表里,并通过DownloadInfo自身来启动下载。(PS:下载就启动了吗?)

3、清理被删除的DownloadInfo。其实这也算一个比较大的功能,因为其实一个下载涉及到了三方面的数据,媒体库,下载数据库,还有下载的文件。三个都是需要同步删除的。




















你可能感兴趣的:(DownloadManager之DownloadService浅析)