編輯:Android資訊
在Android中任何耗時的操作都不能放在UI主線程中,所以耗時的操作都需要使用異步實現。同樣的,在ContentProvider中也可能存在耗時操作,這時也該使用異步操作,而3.0之後最推薦的異步操作就是Loader。它可以方便我們在Activity和Fragment中異步加載數據,而不是用線程或AsyncTask,他的優點如下:
PS:由於在我們現在的多個項目中都大量的使用了Loader來處理數據加載(而且由於粗心跳過幾個坑,譬如Loader ID重復導致數據邏輯異常、多線程中restartLoader導致Loader拋出異常(最後保證都在UI線程中執行即可)等),所以接下來我們進行下使用及源碼淺析。
PPPS:前方高能,文章巨長,請做好心理准備(您可以選擇通過左上角目錄點擊索引到感興趣的章節直接查看,或者,或者,或者直接高能往下看)。
該基礎實例講解完全來自於官方文檔,詳細可以點擊我查看英文原文。
既然接下來准備要說說他的使用強大之處了,那不妨我們先來一張圖直觀的感性認識下不用Loader(左)與用Loader(右)對我們開發者及代碼復雜度和框架的影響吧,如下:
如下是我們開發中常用的一些Loader相關接口:
在我們開發的一個App裡,使用Loader時常規的步驟包含如下一些操作需求:
下面我們看下具體流程。
一個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()方法有三個參數:
上面initLoader()方法的調用確保了一個Loader被初始化和激活的狀態,該方法的調運有如下兩種結果:
可以看見通過initLoader()方法可以將LoaderManager.LoaderCallbacks實例與Loader進行關聯,且當Loader的狀態變化時就被回調。所以說,如果調用者正處於其開始狀態並且被請求的Loader已經存在,且已產生了數據,那麼系統會立即調用onLoadFinished()(在initLoader()調用期間),所以你必須考慮到這種情況的發生。
當然了,intiLoader()會返回一個創建的Loader,但是你不用獲取它的引用,因為LoadeManager會自動管理該Loader的生命周期,你只用在它回調提供的生命周期方法中做自己數據邏輯的處理即可。
通過上面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方法類似,就不再羅嗦了。
LoaderManager.LoaderCallbacks是LoaderManager的回調交互接口。LoaderManager.LoaderCallbacks包含如下三個方法:
當你嘗試使用一個Loader(譬如通過initLoader()方法),它會檢查給定Loader的ID是否存在,如果不存在就觸發LoaderManager.LoaderCallbacks裡的onCreateLoader()方法創建一個新Loader。創建新Loader實例典型的做法就是通過CursorLoader類創建,不過你也可以自定義一個繼承自Loader的子類來實現自己的Loader。
下面的例子中我們通過onCreateLoader()回調創建一個CursorLoader實例,使用CursorLoader的構造方法創建實例時需要一些參數去查詢一個ContentProvider。具體參數如下:
// 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"); }
當創建好的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); }
當實例化好的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); }
下面這個實例是一個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的高級用法就會覺得得心應手許多。
和上面的基本使用介紹一樣,關於Loader的源碼淺析過程會涉及到Activity、Fragment、LoaderManager、Loader、AsyncLoader、CursorLoader等類。所以我們分析的過程還是和以前一樣,依據使用順序進行分析。
我們在分析之前先來看一個Loader框架概要圖,如下:
通過上面圖和前面的基礎實例你會發現Loader的框架和各個類的職責都很明確。Activity和Fragment管理LoaderManager,LoaderManager管理Loader,Loader得到數據後觸發在LoaderManager中實現的Loader的callback接口,LoaderManager在接收到Loader的callback回傳調運時觸發我們Activity或Fragment中實現的LoaderManager回調callback接口,就這樣就實現了Loader的所有功能,而我們平時寫代碼一般只用關心LoaderManager的callback實現即可;對於自定義Loader可能還需要關心AsyncTaskLoader子類的實現。
首先我們都知道,在使用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,可以發現他們各自的管理范圍都是十分的清晰明了的。
上面分析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對象。
上面分析了Activity及Fragment管理了LoaderManager的相關方法,LoaderManager管理了Loader的相關方法,那麼接下來我們就來看看這個被管理的終極目標Loader是咋回事,還有他的子類咋回事。
先來看看我畫的一張關系圖,如下:
我去,這圖現在看可能有些嚇人,我們還是先來慢慢分析一下再說吧。
我們先來看看這個Loader基類吧,該類核心方法及內部類結構圖如下:
代碼分析如下:
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子類的實現邏輯。
上面既然說了Loader類的作用主要是規定接口,同時供LoaderManager管理,那LoaderManager管理的Loader自然需要做一些事情,也就是說我們需要繼承Loader實現一些邏輯操作。然而好在系統API已經幫我們實現了一些簡單的封裝實現,我們這裡就先來看下Loader的直接子類AsyncTaskLoader吧,先來看下該抽象子類的方法及內部類粗略圖,如下:
代碼分析如下:
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即可,後面會給出例子的。
有了上面繼承自Loader的抽象AsyncTaskLoader,接下來我們就來看看SDK為我們提供的抽象AsyncTaskLoader實現類CursorLoader,我們先來粗略看看該類的方法圖,如下:
具體代碼分析如下:
//繼承自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的封裝大大簡化了應用開發者代碼的復雜度;它完全就是一個異步的數據庫查詢瑞士軍刀,沒有啥特別需要分析的地方,所以不再過多說明。
通過上面我們的源碼分析和分析前那副圖可以總結如下結論:
好了,至此你會發現Loader真的很牛叉,No!應該是Google的工程師真的很牛叉,架構真的很贊,值得推薦。
上面對於Loader的基礎使用及源碼框架都進行了簡單分析,有了上面的鋪墊我們再回過頭來看看我們開發中的一些高級技巧,通過這些高級技巧不僅是對前面源碼分析的實例驗證,也是對自己知識的積累。
在我們使用CurSorLoader時大家都會考慮一種情況的處理—–當數據庫發生變化時如何自動刷新當前UI。呵呵,我們先來說說這個原理,數據庫在數據改變時通過ContentPorvider和ContentResolver發出通知,接著ContentProvider通知Cursor的觀察者數據發生了變化,然後Cursor通知CursorLoader的觀察者數據發生了變化,接著CursorLoader通過ContentProvider加載新數據,完事調用CursorAdapter的changeCursor()用新數據替換舊數據顯示。
這個過程具體的實現步驟如下:
通過上面兩步我們就能享受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中不再卡頓。
我們目前的項目其實都使用了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()方法,所以能夠自動刷新,不多解釋了。
可能看到這裡你更加會舉一反三的反駁一句了,上面搞了半天都是和數據庫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); } }
強大的一逼!這下滿技能,不解釋,自己看。
通過前面基礎實例、源碼分析、進階演示你會發現Loader的真的非常好用,非常牛逼,牛逼的我不想再解釋啥了,自己體會吧。
PS:之前看見微博上有人討論AsyncTaskLoader與AsyncTask的區別,這下徹底明朗了,看完源碼我們再回過頭來總結性的說說他們二者區別,如下:
好了,該撕逼的也撕了,該裝逼的也裝了,該分析的也分析了,該學習的也學到了,接下來就是看自己如何帶著Loader去叱詫風雲了。
1. 功能介紹 AndroidEventBus是一個Android平台的事件總線庫, 它簡化了Activity、Fragment、Service等組件或者對象之間
一、概述 Android開發中,網絡請求是很重要的一部分,而緩存網絡請求來的圖片或者響應結果字符串或者結果流,既可以省流量,同時也可以幫助我們解決無網或弱網情況
android M 的名字官方剛發布不久,最終正式版即將來臨! android在不斷發展,最近的更新 M 非常不同,一些主要的變化例如運行時權限將有顛覆性影響。驚
2015年伊始,Google發布了關於Android性能優化典范的專題, 一共16個短視頻,每個3-5分鐘,幫助開發者創建更快更優秀的Android App。課程