天天看點

LoaderManager&Loader源碼剖析(2) – LoaderManager對Loader的主要管理

 閱讀本文前,請閱讀上一篇文章《LoaderManager&Loader源碼剖析(1) – Activity對LoaderManager的管理》。

我們在使用LoaderManager時,通常做的第一件事就是:

// Prepare the loader.  Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
           

是以我們先從getLoaderManager()開始吧。根據上篇的文章介紹,我們知道,最終會調用到:

class LoaderManagerImpl extends LoaderManager {
……
final String mWho;
    Activity mActivity;
boolean mStarted;
 LoaderManagerImpl(String who, Activity activity, boolean started) {
        mWho = who;
        mActivity = activity;
        mStarted = started;
}
……
           

mWho:表明LoaderManager的身份,在這裡傳入的參數值是”(root)”。

mActivity: Activity引用。屬于哪個Activity。

mStarted:是否啟動。這個值由Activity負責傳遞的。從上篇文章介紹我們知道:

public LoaderManager getLoaderManager() {
        if (mLoaderManager != null) {
            return mLoaderManager;
        }
        mCheckedForLoaderManager = true;
        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
        return mLoaderManager;
    }
    
    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
        if (mAllLoaderManagers == null) {
            mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
        }
        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
        if (lm == null) {
            if (create) {
                lm = new LoaderManagerImpl(who, this, started);
                mAllLoaderManagers.put(who, lm);
            }
        } else {
            lm.updateActivity(this);
        }
        return lm;
    }
           

LoaderManagerImpl的第3個參數started實際上是由如下代碼中的mLoadersStarted傳遞的:

mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);

mLoadersStarted而在Activity中的onStart()指派為true的。這恰恰也說明了mLoadersStarted有一個重要作用,那就是表明Activity實際上已經進入started狀态,此時應該啟動所有的Loader了。

Activity建立完LoaderManager以後,我們通常會接着調用LoaderManager. initLoader,那麼我們接着來看一下initLoader方法:

@SuppressWarnings("unchecked")
    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }
        
        LoaderInfo info = mLoaders.get(id);
        
        if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);

        if (info == null) {
            // Loader doesn't already exist; create.
            info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            if (DEBUG) Log.v(TAG, "  Created new loader " + info);
        } else {
            if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
        }
        
        if (info.mHaveData && mStarted) {
            // If the loader has already generated its data, report it now.
            info.callOnLoadFinished(info.mLoader, info.mData);
        }
        
        return (Loader<D>)info.mLoader;
}
           

mCreatingLoader是一個保護型變量,避免在callback中重複循環調用initLoader方法。我們第一次建立id值的Loader時,需要調用createAndInstallLoader建立一個LoaderInfo執行個體來代表ID為id的Loader,否則隻是簡單更新一下mCallbacks引用。

至于createAndInstallLoader,請看代碼:

private LoaderInfo createAndInstallLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        try {
            ……
            LoaderInfo info = createLoader(id, args, callback);
            installLoader(info);
            ……
        } finally {
            ……
        }
    }
           

上面做了兩件事:1)createLoader:建立一個Loader,當然代表這個Loader執行個體的是一個内部類LoaderInfo。2)installLoader:安裝這個Loader。

接着看createLoader():

private LoaderInfo createLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        Loader<Object> loader = callback.onCreateLoader(id, args);
        info.mLoader = (Loader<Object>)loader;
        return info;
    }
           

做了2件事:

1) 建立一個LoaderInfo, Loader id,參數args,還有callback都儲存了起來;

2) 調用callback的onCreateLoader,将使用者建立的Loader執行個體引用儲存到info.mLoader成員中;

再看installLoader:

void installLoader(LoaderInfo info) {
        mLoaders.put(info.mId, info);
        if (mStarted) {
            // The activity will start all existing loaders in it's onStart(),
            // so only start them here if we're past that point of the activitiy's
            // life cycle
            info.start();
        }
    }
           

原來調用了剛才建立的LoaderInfo的start方法來啟動loader啊:

void start() {            
            ……
                if (!mListenerRegistered) {
                    mLoader.registerListener(mId, this);
                    mLoader.registerOnLoadCanceledListener(this);
                    mListenerRegistered = true;
                }
                mLoader.startLoading();
            }
        }
           

上面做了2件事:

1)       注冊監聽接口:registerListener注冊了接口onLoadComplete(),registerOnLoadCanceledListener注冊了接口onLoadCanceled(),這兩個方法有什麼用呢?我們暫不去分析源碼,否則縱深分析,最後必定毫無頭緒,目前隻分析LoaderManager吧。那麼我們先看看Loader的SDK描述即可:

public void registerListener (int id, OnLoadCompleteListener<D> listener)

Added in API level 11

注冊一個接口類,當加載完成時,接收回調。這個回調發生在主線程中,是以直接将結果傳遞給視圖元件是安全的。

必須在主線程中調用。

publicvoid registerOnLoadCanceledListener (OnLoadCanceledListener<D> listener)

Added in API level 16

注冊一個接口類,當加載取消時,接收回調。這個回調發生在主線程中,是以直接将結果傳遞給視圖元件是安全的。

必須在主線程中調用。

參數
listener 注冊的監聽類.

2)       mLoader.startLoading:啟動Loader,開始背景資料的加載。我們看看SDK是怎麼解釋這個方法調用的:

public final void startLoading ()

Added in API level 11

當關聯的fragment/activity啟動時,這個函數會被LoaderManager自動調用。如果你使用了LoaderManager來管理Loader的話,請千萬不要自己直接調用這個方法,否則的話,你會破壞Loader的管理。開始異步加載Loader資料。當結果資料準備好的時候,會在主線程中調用相關回調。如果之前的加載已經完成,并且結果資料依然有效的話,那麼結果會被直接傳給回調方法。這個Loader會監聽資料源的變化,如果資料源發生變化,它會調用回調來通知變化。調用 stopLoading() 會停止回調傳送。

本函數會首先更新Loader的内部狀态,這樣函數isStarted()和isReset()就能夠傳回正确的狀态值。接着會調用onStartLoading()實作。

必須在主線程中調用。

再看LoaderManager上面的initLoader方法:

if (info.mHaveData && mStarted) {
            // If the loader has already generated its data, report it now.
            info.callOnLoadFinished(info.mLoader, info.mData);
        }
           

如果目前的Loader已經加載完資料了,并且已經是開始狀态,那麼會調用callOnLoadFinished(),其中調用了使用者的回調onLoadFinished()回調方法,通知使用者加載完成。

當然了,第一次啟動Loader,資料肯定還未加載,那麼我們在Loader加載完成後,會收到回調LoaderInfo類的 onLoadComplete方法,這個方法是在上面的LoaderInfo.start()方法中注冊的。那麼onLoadComplete做了哪些事情呢:

@Override
        public void onLoadComplete(Loader<Object> loader, Object data) {
            ……
            
            LoaderInfo pending = mPendingLoader;
            if (pending != null) {
                //(1)我們在啟動新的Loader的時候,可能一些舊的Loader正在加載,此時我們等待它們完成,然後直接銷毀即可,然後再啟動新的Loader.
                if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
                mPendingLoader = null;
                mLoaders.put(mId, null);
                destroy();
                installLoader(pending);
                return;
            }
            
            // (2)通知使用者加載完成,并把新資料傳遞給使用者,如果你啟動了多個loader,可以使用loader中的getId()方法來獲得ID進行區分。當然了,傳遞的判斷條件是兩個,第1就是新資料與舊資料不同,或者第2還未傳遞過資料,即mHaveData = false。
            if (mData != data || !mHaveData) {
                mData = data;
                mHaveData = true;
                if (mStarted) {
                    callOnLoadFinished(loader, data);
                }
            }

            //if (DEBUG) Log.v(TAG, "  onLoadFinished returned: " + this);

            // (3)如果你又調用了LoaderManager.restartLoader()的話,那麼那些已經加載完成的相同ID的Loader就會進入不活動狀态,此時也需要調用它們的destory()進行銷毀。
            LoaderInfo info = mInactiveLoaders.get(mId);
            if (info != null && info != this) {
                info.mDeliveredData = false;
                info.destroy();
                mInactiveLoaders.remove(mId);
            }

           ……
        }
           

請看上面代碼中标注的(1),(2),(3),這些都是某個Loader加載完畢時需要做的3件事。請注意它們的判斷條件。其中涉及另外兩個LoaderInfo中的方法callOnLoadFinished()和destroy()。一個是通知使用者新資料的加載完成,一個是銷毀舊的或無用的資料。

我們先來看看callOnLoadFinished(),代碼如下:

void callOnLoadFinished(Loader<Object> loader, Object data) {
            …
//通知使用者回調,傳遞加載的新資料
      mCallbacks.onLoadFinished(loader, data);
               … 
}
           

再看一下LoaderInfo.destroy():

void destroy() {
            ……
                try {
//(1)通知使用者回調,Loader被重置了,此時mLoader相關的資料都不可用了,應盡快解綁定此Loader。
                    mCallbacks.onLoaderReset(mLoader);
                } finally {
                    if (mActivity != null) {
                        mActivity.mFragments.mNoTransactionsBecause = lastBecause;
                    }
                }
            }
            ……
//(2)通知調用Loader的reset()方法,重置Loader。
                mLoader.reset();
            ……
            if (mPendingLoader != null) {
                mPendingLoader.destroy();
            }
        }
           

上面主要做了2件事,1是通知使用者解除綁定舊Loader,一個是通知Loader重置資料,比如CursorLoader就會關閉遊标。

Loader的reset()方法我們暫不做研究。但是可以檢視一下SDK描述:

public void reset ()

Added in API level 11

此方法會更新Loader内部狀态,這樣它的isStarted()和isReset()方法就能傳回正确的值。接下來會調用Loader實作類的onReset()方法。

此方法必須在UI主線程中調用。

原來reset會先設定狀态,然後會調用Loader實作類的onReset()方法。比如CursorLoader的onReset()此時就會被調用,或者你自定義Loader的onReset()方法。

通常情況下,使用者經常會調用LoaderManager.restartLoader()方法,比如根據使用者輸入的文本進行某種資料的查找,如果使用者的查找文本發生變化的話,那麼使用者就會調用restartLoader()來進行重新查找的。

public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        ……
        //(1)我們首先需要先看一看是否已經建立過此id的Loader。
        LoaderInfo info = mLoaders.get(id);
        if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
        if (info != null) {
            LoaderInfo inactive = mInactiveLoaders.get(id);
		    //檢視是否有同id的舊Loader存在
            if (inactive != null) {
                if (info.mHaveData) {
                    // This loader now has data...  we are probably being
                    // called from within onLoadComplete, where we haven't
                    // yet destroyed the last inactive loader.  So just do
                    // that now.
                    if (DEBUG) Log.v(TAG, "  Removing last inactive loader: " + info);
				   //(2)銷毀那些舊Loader。
                    inactive.mDeliveredData = false;
                    inactive.destroy();
				   //舍棄目前使用的Loader,這樣此Loader就不會再向使用者傳送資料了。
				   //因為此時重新開機了該Loader,舊資料無需傳遞。
                    info.mLoader.abandon();
                    mInactiveLoaders.put(id, info);
                } else {
                    // We already have an inactive loader for this ID that we are
                    // waiting for!  What to do, what to do...
                    if (!info.mStarted) {
                        //(3)如果此id的Loader尚未啟動,那麼隻需銷毀即可。
                        if (DEBUG) Log.v(TAG, "  Current loader is stopped; replacing");
                        mLoaders.put(id, null);
                        info.destroy();
                    } else {
                        // (4)此時,說明一個同id的Loader正在加載資料,但是尚未加載完成,此時我們調用它的cancel()通知退出加載。
                        if (DEBUG) Log.v(TAG, "  Current loader is running; attempting to cancel");
                        info.cancel();
                        if (info.mPendingLoader != null) {
                            if (DEBUG) Log.v(TAG, "  Removing pending loader: " + info.mPendingLoader);
                            info.mPendingLoader.destroy();
                            info.mPendingLoader = null;
                        }
                        if (DEBUG) Log.v(TAG, "  Enqueuing as new pending loader");
					   //建立一個此id值的Loader,并賦給mPendingLoader,也許你會問為什麼不直接啟動它呢,
//别忘了,此時已經有一個Loader在加載資料了,而且我們已經調用它的cancel來通知它退出加載,當然,通常情況下,
//這個Loader也許剛好加載完成,那麼會收到onLoadComplete()回調,也許正常退出加載,此時收到回調onLoadCanceled(),
//是以在這兩個回調裡,我們再啟動mPendingLoader,不信的話,參考一下這兩個方法,就能找到installLoader(pending)。
                        info.mPendingLoader = createLoader(id, args, 
                                (LoaderManager.LoaderCallbacks<Object>)callback);
                        return (Loader<D>)info.mPendingLoader.mLoader;
                    }
                }
            } else {
                // (5)通知Loader的舍棄資料,因為此時資料應該無效了。另外别忘了儲存這個舊Loader,這樣當新資料到來的時候,就可以銷毀舊資料了。
                if (DEBUG) Log.v(TAG, "  Making last loader inactive: " + info);
                info.mLoader.abandon();
                mInactiveLoaders.put(id, info);
            }
        }
        
	    //(6)如果目前還未建立過Loader,那麼就是簡單地建立并啟動一個id值的Loader即可。
        info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        return (Loader<D>)info.mLoader;
    }
           

上面總共做了6件事。我們看看LoaderInfo的cancel()方法做了什麼事情呢。

void cancel() {
            if (DEBUG) Log.v(TAG, "  Canceling: " + this);
            if (mStarted && mLoader != null && mListenerRegistered) {
                //調用Loader.cancelLoad()來停止資料加載。傳回值為false時,說明此Loader已經加載完資料了,或者不能立即終止,那麼我們直接調用onLoadCanceled()方法。
                if (!mLoader.cancelLoad()) {
                    onLoadCanceled(mLoader);
                }
            }
        }
           

另外我們還需要看一下Loader的abandon()做了什麼事情:

public void abandon ()

Added in API level 11

在reset()方法之前調用,通知Loader儲存目前狀态,并且不再傳遞任何新資料給使用者。