Android應用Loaders全面詳解及原始碼淺析

工匠若水發表於2016-01-21

1 背景

在Android中任何耗時的操作都不能放在UI主執行緒中,所以耗時的操作都需要使用非同步實現。同樣的,在ContentProvider中也可能存在耗時操作,這時也該使用非同步操作,而3.0之後最推薦的非同步操作就是Loader。它可以方便我們在Activity和Fragment中非同步載入資料,而不是用執行緒或AsyncTask,他的優點如下:

  • 提供非同步載入資料機制;
  • 對資料來源變化進行監聽,實時更新資料;
  • 在Activity配置發生變化(如橫豎屏切換)時不用重複載入資料;
  • 適用於任何Activity和Fragment;

PS:由於在我們現在的多個專案中都大量的使用了Loader來處理資料載入(而且由於粗心跳過幾個坑,譬如Loader ID重複導致資料邏輯異常、多執行緒中restartLoader導致Loader丟擲異常(最後保證都在UI執行緒中執行即可)等),所以接下來我們進行下使用及原始碼淺析。

PPPS:前方高能,文章巨長,請做好心理準備(您可以選擇通過左上角目錄點選索引到感興趣的章節直接檢視,或者,或者,或者直接高能往下看)。

2 基礎使用例項

該基礎例項講解完全來自於官方文件,詳細可以點選我檢視英文原文

既然接下來準備要說說他的使用強大之處了,那不妨我們先來一張圖直觀的感性認識下不用Loader(左)與用Loader(右)對我們開發者及程式碼複雜度和框架的影響吧,如下:

Android應用Loaders全面詳解及原始碼淺析

2-1 Loader API概述說明

如下是我們開發中常用的一些Loader相關介面:

Class/Interface Description
LoaderManager 一個與Activity、Fragment關聯的抽象類,用於管理一個或多個Loader例項。每個Activity或Fragment只能有一個LoaderManager,而一個LoaderManager可以有多個Loader。
LoaderManager.LoaderCallbacks 用於和LoaderManager互動的回撥介面。譬如,可以使用onCreateLoader()建立一個新的Loader。
AsyncTaskLoader 抽象的Loader,提供一個AsyncTask繼承實現。
CursorLoader AsyncTaskLoader的子類,用於向ContentResover請求返回一個Cursor。該類以標準遊標查詢實現了Loader協議,使用後臺執行緒進行查詢,使用這個Loader是從ContentProvider載入非同步資料最好的方式。

2-2 在應用中使用Loader

在我們開發的一個App裡,使用Loader時常規的步驟包含如下一些操作需求:

  • 一個Activity或Fragment;
  • 一個LoaderManager例項;
  • 一個CursorLoader,從ContentProvider載入資料;
  • 一個LoaderManager.LoaderCallbacks實現,建立新Loader及管理已存在Loader;
  • 一個組織Loader資料的Adapter,如SimpleCursorAdapter;

下面我們看下具體流程。

2-2-1 啟動一個Loader(initLoader)

一個Activity或Fragment中LoaderManager管理一個或多個Loader例項,每個Activity或Fragment只有一個LoaderManager,我們可以在Activity的onCreate()或Fragment的onActivityCreated()裡初始化一個Loader。例如:

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

可以看見上面的initLoader()方法有三個引數:

  • 第一個引數代表當前Loader的ID;
  • 第二個引數代表提供給Loader建構函式的引數,可選;
  • 第三個引數代表LoaderManager.LoaderCallbacks的回撥實現;

上面initLoader()方法的呼叫確保了一個Loader被初始化和啟用的狀態,該方法的調運有如下兩種結果:

  • 如果代表該Loader的ID已經存在,則後面建立的Loader將直接複用已經存在的;
  • 如果代表該Loader的ID不存在,initLoader()會觸發LoaderManager.LoaderCallbacks回撥的onCreateLoader()方法建立一個Loader;

可以看見通過initLoader()方法可以將LoaderManager.LoaderCallbacks例項與Loader進行關聯,且當Loader的狀態變化時就被回撥。所以說,如果呼叫者正處於其開始狀態並且被請求的Loader已經存在,且已產生了資料,那麼系統會立即呼叫onLoadFinished()(在initLoader()呼叫期間),所以你必須考慮到這種情況的發生。

當然了,intiLoader()會返回一個建立的Loader,但是你不用獲取它的引用,因為LoadeManager會自動管理該Loader的生命週期,你只用在它回撥提供的生命週期方法中做自己資料邏輯的處理即可。

2-2-2 重啟一個Loader(restartLoader)

通過上面initLoader()方法介紹我們可以知道initLoader調運後要麼得到一個ID已存在的Loader,要麼建立一個新的Loader;但是有時我們想丟棄舊資料然後重新開始建立一個新Loader,這可怎麼辦呢?別擔心,要丟棄舊資料呼叫restartLoader()即可。例如,SearchView.OnQueryTextListener的實現重啟了Loader,當使用者查詢發生變化時Loader需要重啟,如下:

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed. Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

上面方法的引數啥的和再上面的init方法類似,就不再羅嗦了。

2-2-3 使用LoaderManager Callbacks

LoaderManager.LoaderCallbacks是LoaderManager的回撥互動介面。LoaderManager.LoaderCallbacks包含如下三個方法:

  • onCreateLoader()
    例項化並返回一個新建立給定ID的Loader物件;
  • onLoadFinished()
    當建立好的Loader完成了資料的load之後回撥此方法;
  • onLoaderReset()
    當建立好的Loader被reset時呼叫此方法,這樣保證它的資料無效;
2-2-3-1 onCreateLoader說明

當你嘗試使用一個Loader(譬如通過initLoader()方法),它會檢查給定Loader的ID是否存在,如果不存在就觸發LoaderManager.LoaderCallbacks裡的onCreateLoader()方法建立一個新Loader。建立新Loader例項典型的做法就是通過CursorLoader類建立,不過你也可以自定義一個繼承自Loader的子類來實現自己的Loader。

下面的例子中我們通過onCreateLoader()回撥建立一個CursorLoader例項,使用CursorLoader的構造方法建立例項時需要一些引數去查詢一個ContentProvider。具體引數如下:

  • uri
    準備獲取內容的URI。
  • projection
    要返回的列key list,null表示返回所有列,但是返回所有列很多時候會降低效能;
  • selection
    要返回的行過濾,也就是SQL中的WHERE語句,null代表返回uri指定的所有行;
  • selectionArgs
    用來替換上面selection中包含的“?”;
  • sortOrder
    結果的行排序,也就是SQL中的ORDER BY,傳遞null則無序;
 // If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created. This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
2-2-3-2 onLoadFinished說明

當建立好的Loader完成資料載入時回撥此方法,我們要確保該方法在Loader釋放現有維持的資料之前被呼叫。在這裡我們應該移除所有對舊資料的使用(因為舊資料不久就會被釋放),但是不用釋放舊資料,因為Loader會幫我們完成舊資料的釋放。

Loader一旦知道App不再使用舊資料就會釋放掉。例如,如果資料來自CursorLoader裡的一個Cursor,我們不應該自己在程式碼中呼叫close()方法;如果一個Cursor正在被放置到一個CursorAdapter時我們應當使用swapCursor()進行新資料交換,這樣正在被放置的舊的Cursor就不會被關掉,也就不會導致Adapter的載入異常。

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    mAdapter.swapCursor(data);
}
2-2-3-3 onLoaderReset說明

當例項化好的Loader被重啟時該方法被回撥,這裡會讓Loader的資料置於無效狀態。這個回撥方法其實就是為了告訴我們啥時候資料要被釋放掉,所以我們應該在這個時候移除對它的引用。如下移除例項:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed. We need to make sure we are no
    // longer using it.
    mAdapter.swapCursor(null);
}

2-2-4 Loader使用例項實戰

下面這個例項是一個Fragment,模擬的是用ListView顯示通訊錄的實時匹配查詢結果,使用CursorLoader管理通訊錄Provider查詢。如下原始碼,比較簡單,註釋也很豐富了,所以不過多解釋:

public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String mCurFilter;

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Give some text to display if there is no data. In a real
        // application this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);

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

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed. Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in. (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed. We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

到此整個Loader基礎使用就介紹完了,關於Loader的高階功能,譬如自定義Loader等內容這裡先不貼程式碼說明,因為在這裡一下子說完都會覺得蒙圈,而且接受難度也比較大,所以我們在上面這些基礎鋪墊之後乘熱先來原始碼淺析,有了原始碼淺析把持住全域性結構後再去用Loader的高階用法就會覺得得心應手許多。

3 原始碼淺析

和上面的基本使用介紹一樣,關於Loader的原始碼淺析過程會涉及到Activity、Fragment、LoaderManager、Loader、AsyncLoader、CursorLoader等類。所以我們分析的過程還是和以前一樣,依據使用順序進行分析。

我們在分析之前先來看一個Loader框架概要圖,如下:

Android應用Loaders全面詳解及原始碼淺析

通過上面圖和前面的基礎例項你會發現Loader的框架和各個類的職責都很明確。Activity和Fragment管理LoaderManager,LoaderManager管理Loader,Loader得到資料後觸發在LoaderManager中實現的Loader的callback介面,LoaderManager在接收到Loader的callback回傳調運時觸發我們Activity或Fragment中實現的LoaderManager回撥callback介面,就這樣就實現了Loader的所有功能,而我們平時寫程式碼一般只用關心LoaderManager的callback實現即可;對於自定義Loader可能還需要關心AsyncTaskLoader子類的實現。

3-1 Activity及Fragment中LoadManager的管理淺析

首先我們都知道,在使用Loader的第一步就是在Activity或者Fragment中獲取LoaderManager例項,所以我們先來看下Activity和Fragment是如何管理這些LoaderManager的。

先來看看Fragment中的LoaderManager,如下:

final class FragmentState implements Parcelable {
    ......
    LoaderManagerImpl mLoaderManager;
    boolean mLoadersStarted;
    boolean mCheckedForLoaderManager;
    ......
    //fragment中獲取LoaderManager辦法
    public LoaderManager getLoaderManager() {
        //可以看見,一個Fragment只有一個LoaderManager
        if (mLoaderManager != null) {
            return mLoaderManager;
        }
        if (mActivity == null) {
            throw new IllegalStateException("Fragment " + this + " not attached to Activity");
        }
        mCheckedForLoaderManager = true;
        //從Activity中獲取LoaderManager,傳入的mWho為當前Fragment的識別key,然後create傳入true表示建立!!!!!!
        mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
        return mLoaderManager;
    }

    public void onStart() {
        mCalled = true;

        if (!mLoadersStarted) {
            mLoadersStarted = true;
            if (!mCheckedForLoaderManager) {
                mCheckedForLoaderManager = true;
                //如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
                mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
            }
            //生命週期依附上LoaderManager
            if (mLoaderManager != null) {
                mLoaderManager.doStart();
            }
        }
    }

    public void onDestroy() {
        mCalled = true;
        if (!mCheckedForLoaderManager) {
            mCheckedForLoaderManager = true;
            //如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
            mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
        }
        //生命週期依附上LoaderManager
        if (mLoaderManager != null) {
            mLoaderManager.doDestroy();
        }
    }

    void performStart() {
        ......
        mCalled = false;
        onStart();
        ......
        //生命週期依附上LoaderManager
        if (mLoaderManager != null) {
            mLoaderManager.doReportStart();
        }
    }

    void performStop() {
        ......
        mCalled = false;
        onStop();
        ......
        if (mLoadersStarted) {
            mLoadersStarted = false;
            if (!mCheckedForLoaderManager) {
                mCheckedForLoaderManager = true;
                //如果還沒調運過getLoaderManager,那就嘗試獲取LoaderManager,傳入的create為false!!!!!
                mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
            }
            if (mLoaderManager != null) {
                //生命週期依附上LoaderManager
                if (mActivity == null || !mActivity.mChangingConfigurations) {
                    mLoaderManager.doStop();
                } else {
                    mLoaderManager.doRetain();
                }
            }
        }
    }

    void performDestroyView() {
        ......
        mCalled = false;
        onDestroyView();
        ......
        //生命週期依附上LoaderManager
        if (mLoaderManager != null) {
            mLoaderManager.doReportNextStart();
        }
    }
}

從上面可以看出,Fragment在其生命週期內會控制LoaderManager(LoaderManager其實控制了Loader)的doStart、doDestroy等方法,也就是說我們在Fragment中只管通過getLoaderManager方法來獲取LoaderManager例項,然後使用就行,別的Fragment都會幫我們處理OK的。

接下來看看Activity中的LoaderManager,如下:

public class Activity extends ContextThemeWrapper implements ... {
        //mAllLoaderManagers儲存了Activity與Fragment的所有LoaderManager
    ArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;
    LoaderManagerImpl mLoaderManager;
    ......
    //Activity中獲取LoaderManager例項的方法
    public LoaderManager getLoaderManager() {
        //可以看見,一個Activity只有一個LoaderManager
        if (mLoaderManager != null) {
            return mLoaderManager;
        }
        mCheckedForLoaderManager = true;
        //咦?這不就是上面Fragment的getLoaderManager中調運的那個activity中的getLoaderManager嗎,只是和這裡的引數不一樣而已
        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
        return mLoaderManager;
    }
    //Activity與Fragment獲取LoaderManager例項的真正方法!!
    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
        //可見一個Activity維護一個mAllLoaderManagers的MAP
        if (mAllLoaderManagers == null) {
            mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
        }
        //嘗試從快取mAllLoaderManagers的MAP中獲取已經例項化的LoaderManager例項
        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
        if (lm == null) {
            if (create) {
                //如果沒有找到並且需要例項化create(切記這個create引數是很重要的),就調運LoaderManagerImpl構造方法例項化一個LoaderManager物件,然後存入快取mAllLoaderManagers的MAP中
                lm = new LoaderManagerImpl(who, this, started);
                mAllLoaderManagers.put(who, lm);
            }
        } else {
            lm.updateActivity(this);
        }
        return lm;
    }

    void invalidateFragment(String who) {
        if (mAllLoaderManagers != null) {
            LoaderManagerImpl lm = mAllLoaderManagers.get(who);
            if (lm != null && !lm.mRetaining) {
                //生命週期依附上LoaderManager
                lm.doDestroy();
                mAllLoaderManagers.remove(who);
            }
        }
    }

    final void performStop() {
        if (mLoadersStarted) {
            mLoadersStarted = false;
            //生命週期依附上LoaderManager
            if (mLoaderManager != null) {
                //mChangingConfigurations表示如果當前發生了配置變化則為true,否則為false!!!!!!!重點,Loader特性之一
                if (!mChangingConfigurations) {
                    //當前Activity的stop不是由配置變化引起則直接呼叫LoaderManager的doStop()方法!!!!!!
                    mLoaderManager.doStop();
                } else {
                    //當前Activity配置變化,所以需要儲存當前的loaderManager,在Activity恢復時恢復這個LoaderManager!!!!!!
                    mLoaderManager.doRetain();
                }
            }
        }
    ......
    }

    final void performDestroy() {
        ......
        onDestroy();
        //生命週期依附上LoaderManager
        if (mLoaderManager != null) {
            mLoaderManager.doDestroy();
        }
        ......
    }

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        if (mLastNonConfigurationInstances != null) {
            //從mLastNonConfigurationInstances中恢復mAllLoaderManagers(mLastNonConfigurationInstances是從onAttach中恢復的),Activity配置變化時會走這裡!!!!
            mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
        }
        ......
        mCalled = true;
    }

    final void performStart() {
    ......
        if (mAllLoaderManagers != null) {
            final int N = mAllLoaderManagers.size();
            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
            for (int i=N-1; i>=0; i--) {
                loaders[i] = mAllLoaderManagers.valueAt(i);
            }
            //生命週期依附上LoaderManager
            for (int i=0; i<N; i++) {
                LoaderManagerImpl lm = loaders[i];
                //呼叫LoaderManager.finishRetain()以及doReportStart()方法來恢復LoaderManager的狀態!!!!!
                lm.finishRetain();
                lm.doReportStart();
            }
        }
        mActivityTransitionState.enterReady(this);
    }
    //該方法會被ActivityThread類呼叫,且調運時機早於performDestroy()方法!!!!!!
    NonConfigurationInstances retainNonConfigurationInstances() {
        ......
        NonConfigurationInstances nci = new NonConfigurationInstances();
        ......
        //配置變化時儲存mAllLoaderManagers!!!!!!
        nci.loaders = mAllLoaderManagers;
        return nci;
    }
}

通過上面的分析可以發現,Activity其實真正的管理了Activity及Fragment的LoaderManager(Fragment也會管理一部分自己LoaderManager的週期),而LoaderManager又管理了Loader,可以發現他們各自的管理範圍都是十分的清晰明瞭的。

3-2 LoadManager及其實現類LoadManagerImpl的淺析

上面分析Activity及Fragment中獲取LoaderManager例項時已經知道,我們獲取的LoaderManager例項其實就是LoaderManagerImpl物件,而LoaderManagerImpl又是LoaderManager類的子類,所以接下來我們來分析這兩個父子類。

先看下抽象父類LoaderManager,如下:

public abstract class LoaderManager {
    //LoaderManager的回撥介面定義
    public interface LoaderCallbacks<D> {
        public Loader<D> onCreateLoader(int id, Bundle args);
        public void onLoadFinished(Loader<D> loader, D data);
        public void onLoaderReset(Loader<D> loader);
    }
    //下面這些方法沒必要再細說了,上面介紹過的
    public abstract <D> Loader<D> initLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<D> callback);

    public abstract <D> Loader<D> restartLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<D> callback);
    //會觸發回撥的onLoaderReset方法
    public abstract void destroyLoader(int id);

    public abstract <D> Loader<D> getLoader(int id);

    public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args);

    public static void enableDebugLogging(boolean enabled) {
        LoaderManagerImpl.DEBUG = enabled;
    }
}

可以看見LoaderManager抽象類只是定義了一些規範介面而已,那麼接著我們看下抽象類LoaderManager的實現類LoaderManagerImpl,如下:

class LoaderManagerImpl extends LoaderManager {
    static final String TAG = "LoaderManager";
    static boolean DEBUG = false;

    //儲存當前存活的Loader
    final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
    //儲存已經執行完的Loader
    final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(0);

    final String mWho;

    Activity mActivity;
    boolean mStarted;
    boolean mRetaining;
    boolean mRetainingStarted;
    //是否正在建立Loader,多執行緒中同時調運建立會導致異常
    boolean mCreatingLoader;
    //Loader的封裝類
    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
            Loader.OnLoadCanceledListener<Object> {
        final int mId;
        final Bundle mArgs;
        LoaderManager.LoaderCallbacks<Object> mCallbacks;
        Loader<Object> mLoader;
        boolean mHaveData;
        boolean mDeliveredData;
        Object mData;
        boolean mStarted;
        //mRetaining標記Activity配置變化時保持當前Loader,不用銷燬;和上面分析Activity的LoaderManager的retainNonConfigurationInstances方法關聯!!!!!!
        boolean mRetaining;
        boolean mRetainingStarted;
        boolean mReportNextStart;
        boolean mDestroyed;
        boolean mListenerRegistered;

        LoaderInfo mPendingLoader;
        //LoaderInfo構造方法
        public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
            mId = id;
            mArgs = args;
            mCallbacks = callbacks;
        }
        //啟動一個Loader
        void start() {
            //配置改變恢復則不用啟動,用原來的
            if (mRetaining && mRetainingStarted) {
                mStarted = true;
                return;
            }
            //如果已經啟動,則不用再restart了
            if (mStarted) {
                return;
            }

            mStarted = true;

            //如果當前封裝中mLoader為空並且通過構造方法的mCallbacks不為空則回撥onCreateLoader方法建立Loader
            if (mLoader == null && mCallbacks != null) {
               mLoader = mCallbacks.onCreateLoader(mId, mArgs);
            }
            if (mLoader != null) {
                if (mLoader.getClass().isMemberClass()
                        && !Modifier.isStatic(mLoader.getClass().getModifiers())) {
                        //如果當前建立的Loader物件是一個非靜態內部類則拋異常!!!!!!
                    throw new IllegalArgumentException(
                            "Object returned from onCreateLoader must not be a non-static inner member class: "
                            + mLoader);
                }
                if (!mListenerRegistered) {
                    //註冊Loader的監聽方法
                    mLoader.registerListener(mId, this);
                    mLoader.registerOnLoadCanceledListener(this);
                    mListenerRegistered = true;
                }
                //調運Loader的startLoading方法
                mLoader.startLoading();
            }
        }
        //Activity的配置改變時進行標誌位的設定,以便可以儲存,配合上面Activity的分析!!!!!!
        void retain() {
            mRetaining = true;
            ......
        }
        //Activity配置變化後重啟後如果有資料則通知回撥方法,配合上面Activity的分析!!!!!!
        void finishRetain() {
            ......
            if (mStarted && mHaveData && !mReportNextStart) {
                callOnLoadFinished(mLoader, mData);
            }
        }
        //配合上面Activity的分析!!!!!!
        void reportStart() {
            ......
        }
        //停止Loader
        void stop() {
            mStarted = false;
            if (!mRetaining) {
                //如果不是Activity配置變化,即不用儲存則登出掉這些回撥
                if (mLoader != null && mListenerRegistered) {
                    ......
                }
            }
        }
        //取消掉Loader
        void cancel() {
            ......
        }
        //銷燬掉Loader
        void destroy() {
            ......
            if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
                ......
                try {
                    //在destroy時如果有資料存在則呼叫callback的onLoaderReset方法!!!!!!
                    mCallbacks.onLoaderReset(mLoader);
                } finally {
                    ......
                }
            }
            ......
            if (mLoader != null) {
                //登出監聽方法
                if (mListenerRegistered) {
                    ......
                }
                //close Cursor等重置操作
                mLoader.reset();
            }
            if (mPendingLoader != null) {
                mPendingLoader.destroy();
            }
        }
    //Loader被取消時回撥該方法
        @Override
        public void onLoadCanceled(Loader<Object> loader) {
            ......
            LoaderInfo pending = mPendingLoader;
            //執行最新的Loader
            if (pending != null) {
                mPendingLoader = null;
                mLoaders.put(mId, null);
                destroy();
                installLoader(pending);
            }
        }
        //載入完成時回撥
        @Override
        public void onLoadComplete(Loader<Object> loader, Object data) {
            ......
            //執行最新的Loader
            if (pending != null) {
                mPendingLoader = null;
                mLoaders.put(mId, null);
                destroy();
                installLoader(pending);
                return;
            }

            if (mData != data || !mHaveData) {
                mData = data;
                mHaveData = true;
                if (mStarted) {
                    callOnLoadFinished(loader, data);
                }
            }
            ......
        }
        //呼叫onLoadFinished
        void callOnLoadFinished(Loader<Object> loader, Object data) {
            if (mCallbacks != null) {
                ......
                try {
                    //回撥onLoadFinished方法
                    mCallbacks.onLoadFinished(loader, data);
                } finally {
                    ......
                }
                mDeliveredData = true;
            }
        }
    }

    //!!!!!!真正LoaderManagerImpl的構造方法
    LoaderManagerImpl(String who, Activity activity, boolean started) {
        mWho = who;
        mActivity = activity;
        mStarted = started;
    }

    //更新當前Activity引用
    void updateActivity(Activity activity) {
        mActivity = activity;
    }

    //私有的建立Loader方法
    private LoaderInfo createLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        //回撥callback的onCreateLoader方法得到Loader物件
        Loader<Object> loader = callback.onCreateLoader(id, args);
        //把得到的Loader物件包裝成LoaderInfo物件
        info.mLoader = (Loader<Object>)loader;
        return info;
    }

    //包裝了建立Loader與install方法,並將mCreatingLoader標記置位
    private LoaderInfo createAndInstallLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        try {
            mCreatingLoader = true;
            //調運上面的私有建立方法建立LoaderInfo物件
            LoaderInfo info = createLoader(id, args, callback);
            //把建立的LoaderInfo物件傳入installLoader方法
            installLoader(info);
            return info;
        } finally {
            mCreatingLoader = false;
        }
    }

    void installLoader(LoaderInfo info) {
        //將建立的LoaderInfo物件存入mLoaders的Map中
        mLoaders.put(info.mId, info);
        if (mStarted) {
            //如果Activity已經started,則啟動LoaderInfo的start方法
            info.start();
        }
    }

    //public的方法,建立一個Loader,前面介紹過的
    @SuppressWarnings("unchecked")
    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        //如果多執行緒中正在有建立的則丟擲異常(寫程式碼要注意這種情況,尤其是跑Monkey容易丟擲,解決辦法就是保證都在統一執行緒中執行!!!!!!)
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }
        //從現有的Map中嘗試獲取指定ID的LoaderInfo物件
        LoaderInfo info = mLoaders.get(id);
        if (info == null) {
            //發現不存在就調運上面的createAndInstallLoader建立一個
            info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        } else {
            //否則還用當前的Loader,只是重新賦值了callBack而已
            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
        }

        if (info.mHaveData && mStarted) {
            //已經有資料,直接調運LoaderInfo的callOnLoadFinished
            info.callOnLoadFinished(info.mLoader, info.mData);
        }
        //返回Loader物件
        return (Loader<D>)info.mLoader;
    }

    //重新創造Loader,前面介紹過的
    @SuppressWarnings("unchecked")
    public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        if (mCreatingLoader) {
            //如果多執行緒中正在有建立的則丟擲異常(寫程式碼要注意這種情況,尤其是跑Monkey容易丟擲,解決辦法就是保證都在統一執行緒中執行!!!!!!)
            throw new IllegalStateException("Called while creating a loader");
        }

        LoaderInfo info = mLoaders.get(id);
        if (info != null) {
            LoaderInfo inactive = mInactiveLoaders.get(id);
            if (inactive != null) {
                if (info.mHaveData) {
                    //發現是已經執行完的Loader且已經存在的Loader有資料則destroy掉執行完的Loader
                    inactive.mDeliveredData = false;
                    inactive.destroy();
                    info.mLoader.abandon();
                    mInactiveLoaders.put(id, info);
                } else {
                    if (!info.mStarted) {
                        //有相同id的Loader還沒start則destory掉
                        mLoaders.put(id, null);
                        info.destroy();
                    } else {
                        //有一個相同id的Loader正在載入資料,但是還沒載入完,呼叫它的cancel()方法通知取消載入
                        info.cancel();
                        if (info.mPendingLoader != null) {
                            info.mPendingLoader.destroy();
                            info.mPendingLoader = null;
                        }
                        //建立一個指定id的Loader同時賦給mPendingLoader,因為這個時候已經有一個Loader正在載入資料,而且我們已經呼叫了其cancel()方法來通知取消載入
                        info.mPendingLoader = createLoader(id, args, 
                                (LoaderManager.LoaderCallbacks<Object>)callback);
                        //返回建立的Loader
                        return (Loader<D>)info.mPendingLoader.mLoader;
                    }
                }
            } else {
                //終止已存在的Loader
                info.mLoader.abandon();
                mInactiveLoaders.put(id, info);
            }
        }
        //重新建立Loader返回
        info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
        return (Loader<D>)info.mLoader;
    }

    //銷燬指定id的Loader
    public void destroyLoader(int id) {
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }
        //不解釋,單純的destory
        int idx = mLoaders.indexOfKey(id);
        if (idx >= 0) {
            LoaderInfo info = mLoaders.valueAt(idx);
            mLoaders.removeAt(idx);
            info.destroy();
        }
        idx = mInactiveLoaders.indexOfKey(id);
        if (idx >= 0) {
            LoaderInfo info = mInactiveLoaders.valueAt(idx);
            mInactiveLoaders.removeAt(idx);
            info.destroy();
        }
        ......
    }

    //獲取指定id的Loader物件
    @SuppressWarnings("unchecked")
    public <D> Loader<D> getLoader(int id) {
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }
        //優先獲取LoaderInfo中的mPendingLoader
        LoaderInfo loaderInfo = mLoaders.get(id);
        if (loaderInfo != null) {
            if (loaderInfo.mPendingLoader != null) {
                return (Loader<D>)loaderInfo.mPendingLoader.mLoader;
            }
            return (Loader<D>)loaderInfo.mLoader;
        }
        return null;
    }
    ......
}

我勒個去!好長,好累!通過上面粗略的分析你會發現和我們上面基礎例項介紹LoaderManager的方法時描述的一樣,每個方法都有自己的特點,發揮著各自的作用,LoaderManager的實質是將Loader物件轉換為LoaderInfo來進行管理,也就是管理了所有的Loader物件。

3-3 Loader及其實現類的淺析

上面分析了Activity及Fragment管理了LoaderManager的相關方法,LoaderManager管理了Loader的相關方法,那麼接下來我們就來看看這個被管理的終極目標Loader是咋回事,還有他的子類咋回事。

先來看看我畫的一張關係圖,如下:

Android應用Loaders全面詳解及原始碼淺析

我去,這圖現在看可能有些嚇人,我們還是先來慢慢分析一下再說吧。

3-3-1 Loader基類原始碼淺析

我們先來看看這個Loader基類吧,該類核心方法及內部類結構圖如下:

Android應用Loaders全面詳解及原始碼淺析

程式碼分析如下:

public class Loader<D> {
    int mId;
    OnLoadCompleteListener<D> mListener;
    OnLoadCanceledListener<D> mOnLoadCanceledListener;
    Context mContext;
    boolean mStarted = false;
    boolean mAbandoned = false;
    boolean mReset = true;
    boolean mContentChanged = false;
    boolean mProcessingChange = false;
    //資料來源變化監聽器(觀察者模式),實現了ContentObserver類
    public final class ForceLoadContentObserver extends ContentObserver {
        public ForceLoadContentObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            //實質是調運Loader的forceLoad方法
            onContentChanged();
        }
    }

    //Loader載入完成介面,當載入完成時Loader通知loaderManager,loaderManager再回撥我們initLoader方法的callback
    public interface OnLoadCompleteListener<D> {
        public void onLoadComplete(Loader<D> loader, D data);
    }
    //LoaderManager中監聽cancel,同上類似
    public interface OnLoadCanceledListener<D> {
        public void onLoadCanceled(Loader<D> loader);
    }

    //構造方法
    public Loader(Context context) {
        //mContext持有Application的Context,防止洩露記憶體等
        mContext = context.getApplicationContext();
    }

    //載入完成時回撥傳遞載入資料結果,實質是對OnLoadCompleteListener介面方法的封裝
    public void deliverResult(D data) {
        if (mListener != null) {
            mListener.onLoadComplete(this, data);
        }
    }
    //類似同上,對OnLoadCanceledListener的方法的封裝
    public void deliverCancellation() {
        if (mOnLoadCanceledListener != null) {
            mOnLoadCanceledListener.onLoadCanceled(this);
        }
    }

    public Context getContext() {
        return mContext;
    }
    public int getId() {
        return mId;
    }
    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
        mListener = listener;
        mId = id;
    }
    public void unregisterListener(OnLoadCompleteListener<D> listener) {
        mListener = null;
    }
    public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        mOnLoadCanceledListener = listener;
    }
    public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        mOnLoadCanceledListener = null;
    }
    public boolean isStarted() {
        return mStarted;
    }
    public boolean isAbandoned() {
        return mAbandoned;
    }
    public boolean isReset() {
        return mReset;
    }

    //開始載入資料時LoaderManager會呼叫該方法
    public final void startLoading() {
        //設定標記
        mStarted = true;
        mReset = false;
        mAbandoned = false;
        onStartLoading();
    }

    //真正開始載入資料的地方******空方法,子類實現!!!!!!
    protected void onStartLoading() {
    }

    //取消Loader的方法
    public boolean cancelLoad() {
        return onCancelLoad();
    }

    //真正取消的地方******,子類實現!!!!!!return false表示取消失敗(因為已完成或未開始)
    protected boolean onCancelLoad() {
        return false;
    }

    //強制重新Loader,放棄舊資料
    public void forceLoad() {
        onForceLoad();
    }

    //真正重新Loader的地方******空方法,子類實現!!!!!!
    protected void onForceLoad() {
    }

    //同上
    public void stopLoading() {
        mStarted = false;
        onStopLoading();
    }
    protected void onStopLoading() {
    }

    //同上
    public void abandon() {
        mAbandoned = true;
        onAbandon();
    }
    protected void onAbandon() {
    }

    //同上
    public void reset() {
        onReset();
        mReset = true;
        mStarted = false;
        mAbandoned = false;
        mContentChanged = false;
        mProcessingChange = false;
    }
    protected void onReset() {
    }

    //Loader資料變化的一些標記處理
    public boolean takeContentChanged() {
        boolean res = mContentChanged;
        mContentChanged = false;
        mProcessingChange |= res;
        return res;
    }
    public void commitContentChanged() {
        mProcessingChange = false;
    }
    public void rollbackContentChanged() {
        if (mProcessingChange) {
            mContentChanged = true;
        }
    }

    //上面ForceLoadContentObserver內部類的onChange方法調運
    public void onContentChanged() {
        if (mStarted) {
            forceLoad();
        } else {
            mContentChanged = true;
        }
    }

    //一些方便除錯的方法
    public String dataToString(D data)
    public String toString()
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)
}

通過上面粗略的分析可以發現,Loader基類無非也就是一個方法介面的定義類,組織預留了一些方法供LoaderManager去調運處理,同時需要子類實現其提供的一些onXXX方法,以便LoaderManager調運Loader的方法時可以觸發Loader子類的實現邏輯。

3-3-2 AsyncTaskLoader抽象子類原始碼淺析

上面既然說了Loader類的作用主要是規定介面,同時供LoaderManager管理,那LoaderManager管理的Loader自然需要做一些事情,也就是說我們需要繼承Loader實現一些邏輯操作。然而好在系統API已經幫我們實現了一些簡單的封裝實現,我們這裡就先來看下Loader的直接子類AsyncTaskLoader吧,先來看下該抽象子類的方法及內部類粗略圖,如下:

Android應用Loaders全面詳解及原始碼淺析

程式碼分析如下:

public abstract class AsyncTaskLoader<D> extends Loader<D> {
    static final String TAG = "AsyncTaskLoader";
    static final boolean DEBUG = false;
    //LoadTask內部類是對AsyncTask的封裝,實現了Runnable介面
    final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
        ......
        @Override
        protected D doInBackground(Void... params) {
            try {
                //AsyncTask的子執行緒中執行AsyncTaskLoader的onLoadInBackground方法!!!!重點
                D data = AsyncTaskLoader.this.onLoadInBackground();
                //把執行結果資料D返回到UI執行緒
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    throw ex;
                }
                return null;
            }
        }

        /* Runs on the UI thread */
        @Override
        protected void onPostExecute(D data) {
            //AsyncTask子執行緒執行完畢後回撥AsyncTaskLoader的dispatchOnLoadComplete方法
            AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
        }

        /* Runs on the UI thread */
        @Override
        protected void onCancelled(D data) {
            //取消AsyncTask時調運
            AsyncTaskLoader.this.dispatchOnCancelled(this, data);
        }

        //Runnable的實現方法
        @Override
        public void run() {
            waiting = false;
            AsyncTaskLoader.this.executePendingTask();
        }
    ......
    }

    private final Executor mExecutor;

    volatile LoadTask mTask;
    volatile LoadTask mCancellingTask;

    long mUpdateThrottle;
    long mLastLoadCompleteTime = -10000;
    Handler mHandler;
    //public構造方法
    public AsyncTaskLoader(Context context) {
        this(context, AsyncTask.THREAD_POOL_EXECUTOR);
    }

    /** {@hide} 無法被外部調運的構造方法 */
    public AsyncTaskLoader(Context context, Executor executor) {
        super(context);
        mExecutor = executor;
    }

    public void setUpdateThrottle(long delayMS) {
        mUpdateThrottle = delayMS;
        if (delayMS != 0) {
            mHandler = new Handler();
        }
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        //取消當前的Loader
        cancelLoad();
        //新建task並執行
        mTask = new LoadTask();
        executePendingTask();
    }

    @Override
    protected boolean onCancelLoad() {
        ......
    }

    public void onCanceled(D data) {
    }

    //LoadTask的Runnable方法run中執行
    void executePendingTask() {
        if (mCancellingTask == null && mTask != null) {
            if (mTask.waiting) {
                mTask.waiting = false;
                mHandler.removeCallbacks(mTask);
            }
            if (mUpdateThrottle > 0) {
                long now = SystemClock.uptimeMillis();
                if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                    // Not yet time to do another load.
                    mTask.waiting = true;
                    mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                    return;
                }
            }
            //真正的觸發執行AsyncTask方法
            mTask.executeOnExecutor(mExecutor, (Void[]) null);
        }
    }

    void dispatchOnCancelled(LoadTask task, D data) {
        onCanceled(data);
        if (mCancellingTask == task) {
            rollbackContentChanged();
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mCancellingTask = null;
            //觸發Loader的介面方法onLoadCanceled,在LoaderManager中實現
            deliverCancellation();
            executePendingTask();
        }
    }

    void dispatchOnLoadComplete(LoadTask task, D data) {
        if (mTask != task) {
            dispatchOnCancelled(task, data);
        } else {
            if (isAbandoned()) {
                // This cursor has been abandoned; just cancel the new data.
                onCanceled(data);
            } else {
                commitContentChanged();
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mTask = null;
                //觸發Loader的介面方法onLoadComplete,在LoaderManager中實現
                deliverResult(data);
            }
        }
    }
    //需要子類實現!!!!!在子執行緒中執行
    public abstract D loadInBackground();

    //LoadTask(AsyncTask的子執行緒中回撥)中調運
    protected D onLoadInBackground() {
        return loadInBackground();
    }

    //LoadTask(AsyncTask的onCancelLoad中回撥)調運
    public void cancelLoadInBackground() {
    }

    public boolean isLoadInBackgroundCanceled() {
        return mCancellingTask != null;
    }
    //鎖標記處理
    public void waitForLoader() {
        LoadTask task = mTask;
        if (task != null) {
            task.waitForLoader();
        }
    }
}

可以看見上面繼承Loader的AsyncTaskLoader其實質是提供了一個基於AsyncTask工作機制的Loader(子類LoadTask繼承AsyncTask<Void, Void, D>,並且實現了Runable介面,功能十分強大。),但是不可直接用,因為其為abstract抽象類,所以我們需要繼承實現它才可以使用,然而好在系統API已經幫我們提供了他現成的子類CursorLoader,但CursorLoader同時也限制了Loader的泛型資料為Cursor型別。當然了,我們如果想要Loader自己的型別資料那也很簡單—繼承實現AsyncTaskLoader即可,後面會給出例子的。

3-3-3 CursorLoader子類原始碼淺析

有了上面繼承自Loader的抽象AsyncTaskLoader,接下來我們就來看看SDK為我們提供的抽象AsyncTaskLoader實現類CursorLoader,我們先來粗略看看該類的方法圖,如下:

Android應用Loaders全面詳解及原始碼淺析

具體程式碼分析如下:

//繼承自AsyncTaskLoader,資料型別為Cursor的Loader非同步載入實現類
public class CursorLoader extends AsyncTaskLoader<Cursor> {
    //Cursor的子類ForceLoadContentObserver
    final ForceLoadContentObserver mObserver;

    Uri mUri;
    String[] mProjection;
    String mSelection;
    String[] mSelectionArgs;
    String mSortOrder;

    Cursor mCursor;
    CancellationSignal mCancellationSignal;

    /* Runs on a worker thread 最核心的實現方法,在這裡查詢獲取資料 */
    @Override
    public Cursor loadInBackground() {
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            mCancellationSignal = new CancellationSignal();
        }
        try {
            //不過多解釋,耗時的查詢操作
            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                    mSelectionArgs, mSortOrder, mCancellationSignal);
            if (cursor != null) {
                try {
                    // Ensure the cursor window is filled.
                    cursor.getCount();
                    //給Cursor設定觀察者;ContentProvider通知Cursor的觀察者資料發生了改變,Cursor通知CursorLoader的觀察者資料發生了改變,CursorLoader通過ContentProvider重新載入新的資料
                    cursor.registerContentObserver(mObserver);
                } catch (RuntimeException ex) {
                    cursor.close();
                    throw ex;
                }
            }
            return cursor;
        } finally {
            synchronized (this) {
                mCancellationSignal = null;
            }
        }
    }

    @Override
    public void cancelLoadInBackground() {
        super.cancelLoadInBackground();

        synchronized (this) {
            if (mCancellationSignal != null) {
                mCancellationSignal.cancel();
            }
        }
    }

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    public CursorLoader(Context context) {
        super(context);
        mObserver = new ForceLoadContentObserver();
    }

    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        super(context);
        //新建一個當前類(Loader)的內部類物件,資料庫變化時調運ForceLoadContentObserver的onChange方法,onChange調運Loader的onContentChanged方法,onContentChanged調運Loader的forceLoad方法
        mObserver = new ForceLoadContentObserver();
        mUri = uri;
        mProjection = projection;
        mSelection = selection;
        mSelectionArgs = selectionArgs;
        mSortOrder = sortOrder;
    }

    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }

    public Uri getUri() {
        return mUri;
    }

    public void setUri(Uri uri) {
        mUri = uri;
    }

    public String[] getProjection() {
        return mProjection;
    }

    public void setProjection(String[] projection) {
        mProjection = projection;
    }

    public String getSelection() {
        return mSelection;
    }

    public void setSelection(String selection) {
        mSelection = selection;
    }

    public String[] getSelectionArgs() {
        return mSelectionArgs;
    }

    public void setSelectionArgs(String[] selectionArgs) {
        mSelectionArgs = selectionArgs;
    }

    public String getSortOrder() {
        return mSortOrder;
    }

    public void setSortOrder(String sortOrder) {
        mSortOrder = sortOrder;
    }
}

可以發現,CursorLoader的封裝大大簡化了應用開發者程式碼的複雜度;它完全就是一個非同步的資料庫查詢瑞士軍刀,沒有啥特別需要分析的地方,所以不再過多說明。

3-4 Loaders相關原始碼淺析總結

通過上面我們的原始碼分析和分析前那副圖可以總結如下結論:

  • 一次完整的資料載入流程為Activity呼叫LoaderManager的doStart()方法,然後LoaderManager呼叫Loader的startLoading()方法,然後Loader調運AsyncTaskLoader的doingBackground()方法進行耗時資料載入,然後AsyncTaskLoader回撥LoaderManager的complete資料載入完成方法,接著LoaderManager回撥我們在Activity中實現的callback中的onLoadFinish()方法。
  • Acivity和Fragment的生命週期主動管理了LoaderManager,每個Activity用一個ArrayMap的mAllLoaderManager來儲存當前Activity及其附屬Frament的唯一LoaderManager;在Activity配置發生變化時,Activity在destory前會儲存mAllLoaderManager,當Activity再重新建立時,會在Activity的onAttcach()、onCreate()、performStart()方法中恢復mAllLoaderManager。
  • LoaderManager給Activity提供了管理自己的一些方法;同時主動管理了對應的Loader,它把每一個Loader封裝為LoadInfo物件,同時它負責主動調運管理Loader的startLoading()、stopLoading()、,forceLoad()等方法。
  • 由於整個Activity和Fragment主動管理了Loader,所以關於Loader的釋放(譬如CursorLoader的Cursor關閉等)不需要我們人為處理,Loader框架會幫我們很好的處理的;同時特別注意,對於CursorLoader,當我們資料來源發生變化時Loader框架會通過ContentObserver呼叫onContentChanged的forceLoad方法重新請求資料進行回撥重新整理。

好了,至此你會發現Loader真的很牛叉,No!應該是Google的工程師真的很牛叉,架構真的很贊,值得推薦。

4 應用層開發之Loader進階實戰

上面對於Loader的基礎使用及原始碼框架都進行了簡單分析,有了上面的鋪墊我們再回過頭來看看我們開發中的一些高階技巧,通過這些高階技巧不僅是對前面原始碼分析的例項驗證,也是對自己知識的積累。

4-1 ContentPorvider情況下的CurSorLoader自動重新整理

在我們使用CurSorLoader時大家都會考慮一種情況的處理—–當資料庫發生變化時如何自動重新整理當前UI。呵呵,我們先來說說這個原理,資料庫在資料改變時通過ContentPorvider和ContentResolver發出通知,接著ContentProvider通知Cursor的觀察者資料發生了變化,然後Cursor通知CursorLoader的觀察者資料發生了變化,接著CursorLoader通過ContentProvider載入新資料,完事呼叫CursorAdapter的changeCursor()用新資料替換舊資料顯示。

這個過程具體的實現步驟如下:

  1. 對獲取的Cursor資料設定需要監聽的URI(即,在ContentProvider的query()方法或者Loader的loadingBackground()方法中呼叫Cursor的setNotificationUri()方法);
  2. 在ContentProvider的insert()、update()、delete()等方法中呼叫ContentResolver的notifyChange()方法;

通過上面兩步我們就能享受CurSorLoader的自動資料重新整理功能了;可以發現,所謂的CurSorLoader自動重新整理無非就是觀察者模式的框架而已,所以不再過多說明。

特別注意:

有些人覺得為了方便可能會將上面第一步對於Cursor設定監聽直接寫在了ContentProvider的query()方法中,如下:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] 
selectionArgs,String sortOrder) {
    SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
    Cursor cursor = database.query(EmailContent.CONTACT_TABLE, projection,
    selection,selectionArgs, null, null, sortOrder);
    //設定NotificationUri監聽
    cursor.setNotificationUri(contentResolver, EmailContent.MESSAGE);
    return cursor;
}

這裡要提醒的是,這種寫法在某些場合下是不值得推薦的(譬如大規模上千次併發平凡的調運query操作場合),因為效率極低,他會頻繁的通過Binder進行通訊,導致system_server不停的調運GC操作,以至於會使系統卡頓。

PS:因為我以前跳過一次這個坑,平時使用應用沒啥問題,但是當進行壓力測試時卻發現LogCat一直在不停的列印GC,同時導致當前系統卡頓,殺掉應用後系統就不卡了,所以基本懷疑問題就出在了應用中,於是通過很多辦法去查詢(譬如dempsys content去檢視個數),最終發現罪魁禍首是這個監聽頻繁調運導致的,隨將其挪到loadingBackground中不再卡頓。

4-2 不使用ContentPorvider且自定義Loader的情況下自動重新整理

我們目前的專案其實都使用了ContentPorvider實現,所以就是上面講的那些情況。但是你一定會問,如果我們應用的資料不用於應用間共享,使用ContentProvider那得多麻煩啊?我先告訴你,是很麻煩,但是Android提供的CursorLoader的API必須使用ContentProvider才能實現資料載入和自動重新整理。

這時候你指定會說,那還說個屁!哎,別急,你看看下面這段程式碼是否會有所感觸呢,如下:

public NoProviderLoader extends AsyncTaskLoader {
    ......
    ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
    ......
    @Override
    public Cursor loadInBackground() {
        SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
        Cursor cursor = database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
        if (cursor != null) {
            //最重要的兩行程式碼!!!!!!
            cursor.registerContentObserver(mObserver);//給Cursor設定觀察者
            cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//給Cursor設定要觀察的URI
        }

        return cursor;
    }
    ......
}

咦?是不是上面程式碼很奇怪,非同步操作的方法中沒有使用ContentProvider,而是直接讀取了資料庫。握草!這不就是我們剛剛想要的需求麼,它沒有使用ContentProvider提供Cursor資料,同時實現了資料變化自動更新功能。

簡單解釋下上面程式碼的原理吧,我們自定義的NoProviderLoader中定義的ForceLoadContentObserver是Loader的一個內部類,上面原始碼分析已經解釋過了,當資料變化時會調運該類的onChange()方法,實質是調運了Loader的forceLoad()方法,所以能夠自動重新整理,不多解釋了。

4-3 Loader自定義之AsyncTaskLoader衍生

可能看到這裡你更加會舉一反三的反駁一句了,上面搞了半天都是和資料庫Cursor相關的東東,難道Loader就不能非同步處理別的資料結構麼?答案是能,因為你可能已經注意到了Loader和AsyncTaskLoader都是泛型類;既然這樣,那我們找貓畫虎一把唄,仿照CursorLoader自定義一個自己的非同步載入試試,具體實現如下(哈哈,想了又想,這裡還是直接給出官方的自定義AsyncTaskLoader好點,畢竟權威些,詳細點我檢視官方自定義實現Demo):

官方對於查詢已安裝App列表的Loader實現,支援新App安裝後自動重新整理的功能,實現如下:

/** * This class holds the per-item data in our Loader. */
public static class AppEntry {
    public AppEntry(AppListLoader loader, ApplicationInfo info) {
        mLoader = loader;
        mInfo = info;
        mApkFile = new File(info.sourceDir);
    }

    public ApplicationInfo getApplicationInfo() {
        return mInfo;
    }

    public String getLabel() {
        return mLabel;
    }

    public Drawable getIcon() {
        if (mIcon == null) {
            if (mApkFile.exists()) {
                mIcon = mInfo.loadIcon(mLoader.mPm);
                return mIcon;
            } else {
                mMounted = false;
            }
        } else if (!mMounted) {
            // If the app wasn't mounted but is now mounted, reload
            // its icon.
            if (mApkFile.exists()) {
                mMounted = true;
                mIcon = mInfo.loadIcon(mLoader.mPm);
                return mIcon;
            }
        } else {
            return mIcon;
        }

        return mLoader.getContext().getResources().getDrawable(
                android.R.drawable.sym_def_app_icon);
    }

    @Override public String toString() {
        return mLabel;
    }

    void loadLabel(Context context) {
        if (mLabel == null || !mMounted) {
            if (!mApkFile.exists()) {
                mMounted = false;
                mLabel = mInfo.packageName;
            } else {
                mMounted = true;
                CharSequence label = mInfo.loadLabel(context.getPackageManager());
                mLabel = label != null ? label.toString() : mInfo.packageName;
            }
        }
    }

    private final AppListLoader mLoader;
    private final ApplicationInfo mInfo;
    private final File mApkFile;
    private String mLabel;
    private Drawable mIcon;
    private boolean mMounted;
}

/** * Perform alphabetical comparison of application entry objects. */
public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
    private final Collator sCollator = Collator.getInstance();
    @Override
    public int compare(AppEntry object1, AppEntry object2) {
        return sCollator.compare(object1.getLabel(), object2.getLabel());
    }
};

/** * Helper for determining if the configuration has changed in an interesting * way so we need to rebuild the app list. */
public static class InterestingConfigChanges {
    final Configuration mLastConfiguration = new Configuration();
    int mLastDensity;

    boolean applyNewConfig(Resources res) {
        int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
        boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
        if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
                |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
            mLastDensity = res.getDisplayMetrics().densityDpi;
            return true;
        }
        return false;
    }
}

/** * Helper class to look for interesting changes to the installed apps * so that the loader can be updated. */
public static class PackageIntentReceiver extends BroadcastReceiver {
    final AppListLoader mLoader;

    public PackageIntentReceiver(AppListLoader loader) {
        mLoader = loader;
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        mLoader.getContext().registerReceiver(this, filter);
        // Register for events related to sdcard installation.
        IntentFilter sdFilter = new IntentFilter();
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        mLoader.getContext().registerReceiver(this, sdFilter);
    }

    @Override public void onReceive(Context context, Intent intent) {
        // Tell the loader about the change.
        mLoader.onContentChanged();
    }
}

/** * A custom Loader that loads all of the installed applications. */
public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
    final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
    final PackageManager mPm;

    List<AppEntry> mApps;
    PackageIntentReceiver mPackageObserver;

    public AppListLoader(Context context) {
        super(context);

        // Retrieve the package manager for later use; note we don't
        // use 'context' directly but instead the save global application
        // context returned by getContext().
        mPm = getContext().getPackageManager();
    }

    /** * This is where the bulk of our work is done. This function is * called in a background thread and should generate a new set of * data to be published by the loader. */
    @Override public List<AppEntry> loadInBackground() {
        // Retrieve all known applications.
        List<ApplicationInfo> apps = mPm.getInstalledApplications(
                PackageManager.GET_UNINSTALLED_PACKAGES |
                PackageManager.GET_DISABLED_COMPONENTS);
        if (apps == null) {
            apps = new ArrayList<ApplicationInfo>();
        }

        final Context context = getContext();

        // Create corresponding array of entries and load their labels.
        List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
        for (int i=0; i<apps.size(); i++) {
            AppEntry entry = new AppEntry(this, apps.get(i));
            entry.loadLabel(context);
            entries.add(entry);
        }

        // Sort the list.
        Collections.sort(entries, ALPHA_COMPARATOR);

        // Done!
        return entries;
    }

    /** * Called when there is new data to deliver to the client. The * super class will take care of delivering it; the implementation * here just adds a little more logic. */
    @Override public void deliverResult(List<AppEntry> apps) {
        if (isReset()) {
            // An async query came in while the loader is stopped. We
            // don't need the result.
            if (apps != null) {
                onReleaseResources(apps);
            }
        }
        List<AppEntry> oldApps = mApps;
        mApps = apps;

        if (isStarted()) {
            // If the Loader is currently started, we can immediately
            // deliver its results.
            super.deliverResult(apps);
        }

        // At this point we can release the resources associated with
        // 'oldApps' if needed; now that the new result is delivered we
        // know that it is no longer in use.
        if (oldApps != null) {
            onReleaseResources(oldApps);
        }
    }

    /** * Handles a request to start the Loader. */
    @Override protected void onStartLoading() {
        if (mApps != null) {
            // If we currently have a result available, deliver it
            // immediately.
            deliverResult(mApps);
        }

        // Start watching for changes in the app data.
        if (mPackageObserver == null) {
            mPackageObserver = new PackageIntentReceiver(this);
        }

        // Has something interesting in the configuration changed since we
        // last built the app list?
        boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());

        if (takeContentChanged() || mApps == null || configChange) {
            // If the data has changed since the last time it was loaded
            // or is not currently available, start a load.
            forceLoad();
        }
    }

    /** * Handles a request to stop the Loader. */
    @Override protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    /** * Handles a request to cancel a load. */
    @Override public void onCanceled(List<AppEntry> apps) {
        super.onCanceled(apps);

        // At this point we can release the resources associated with 'apps'
        // if needed.
        onReleaseResources(apps);
    }

    /** * Handles a request to completely reset the Loader. */
    @Override protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        // At this point we can release the resources associated with 'apps'
        // if needed.
        if (mApps != null) {
            onReleaseResources(mApps);
            mApps = null;
        }

        // Stop monitoring for changes.
        if (mPackageObserver != null) {
            getContext().unregisterReceiver(mPackageObserver);
            mPackageObserver = null;
        }
    }

    /** * Helper function to take care of releasing resources associated * with an actively loaded data set. */
    protected void onReleaseResources(List<AppEntry> apps) {
        // For a simple List<> there is nothing to do. For something
        // like a Cursor, we would close it here.
    }
}

不用多說,上面Loader為Google出品,強大的不得了,我們完全可以仿寫這個例子實現自己的請求。

如下為官方對該自定義Loader調運的Demo程式碼:

public static class AppListAdapter extends ArrayAdapter<AppEntry> {
    private final LayoutInflater mInflater;

    public AppListAdapter(Context context) {
        super(context, android.R.layout.simple_list_item_2);
        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public void setData(List<AppEntry> data) {
        clear();
        if (data != null) {
            addAll(data);
        }
    }

    /** * Populate new items in the list. */
    @Override public View getView(int position, View convertView, ViewGroup parent) {
        View view;

        if (convertView == null) {
            view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
        } else {
            view = convertView;
        }

        AppEntry item = getItem(position);
        ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
        ((TextView)view.findViewById(R.id.text)).setText(item.getLabel());

        return view;
    }
}

public static class AppListFragment extends ListFragment implements OnQueryTextListener, OnCloseListener, LoaderManager.LoaderCallbacks<List<AppEntry>> {

    // This is the Adapter being used to display the list's data.
    AppListAdapter mAdapter;

    // The SearchView for doing filtering.
    SearchView mSearchView;

    // If non-null, this is the current filter the user has provided.
    String mCurFilter;

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Give some text to display if there is no data. In a real
        // application this would come from a resource.
        setEmptyText("No applications");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new AppListAdapter(getActivity());
        setListAdapter(mAdapter);

        // Start out with a progress indicator.
        setListShown(false);

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

    public static class MySearchView extends SearchView {
        public MySearchView(Context context) {
            super(context);
        }

        // The normal SearchView doesn't clear its search text when
        // collapsed, so we will do this for it.
        @Override
        public void onActionViewCollapsed() {
            setQuery("", false);
            super.onActionViewCollapsed();
        }
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
                | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
        mSearchView = new MySearchView(getActivity());
        mSearchView.setOnQueryTextListener(this);
        mSearchView.setOnCloseListener(this);
        mSearchView.setIconifiedByDefault(true);
        item.setActionView(mSearchView);
    }

    @Override public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed. Since this
        // is a simple array adapter, we can just have it do the filtering.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        mAdapter.getFilter().filter(mCurFilter);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override
    public boolean onClose() {
        if (!TextUtils.isEmpty(mSearchView.getQuery())) {
            mSearchView.setQuery(null, true);
        }
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("LoaderCustom", "Item clicked: " + id);
    }

    @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader with no arguments, so it is simple.
        return new AppListLoader(getActivity());
    }

    @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
        // Set the new data in the adapter.
        mAdapter.setData(data);

        // The list should now be shown.
        if (isResumed()) {
            setListShown(true);
        } else {
            setListShownNoAnimation(true);
        }
    }

    @Override public void onLoaderReset(Loader<List<AppEntry>> loader) {
        // Clear the data in the adapter.
        mAdapter.setData(null);
    }
}

強大的一逼!這下滿技能,不解釋,自己看。

4-4 進階總結

通過前面基礎例項、原始碼分析、進階演示你會發現Loader的真的非常好用,非常牛逼,牛逼的我不想再解釋啥了,自己體會吧。

PS:之前看見微博上有人討論AsyncTaskLoader與AsyncTask的區別,這下徹底明朗了,看完原始碼我們再回過頭來總結性的說說他們二者區別,如下:

class 優勢 劣勢
AsyncTaskLoader 會自動重新整理資料變化;會自動處理Activiy配置變化造成的影響;適合處理純資料載入; 不能實時通知UI重新整理;不能在onLoadFinished時主動切換生命週期(譬如replace Fragment);
AsyncTask 可以與UI實時互動及replace操作; 不會自動處理Activiy配置變化造成的影響;

好了,該撕逼的也撕了,該裝逼的也裝了,該分析的也分析了,該學習的也學到了,接下來就是看自己如何帶著Loader去叱詫風雲了。

相關文章