在看Android的文檔時,看到了這麼一個東西: Loaderjava
到底是什麼東西呢?android
Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:數據庫
一、They are available to every Activity and Fragment. //支持Activity和Fragment異步
二、They provide asynchronous loading of data. //異步下載async
三、They monitor the source of their data and deliver new results when the content changes. //當數據源改變時能及時通知客戶端ide
四、They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data. //發生configuration change時自動重鏈接函數
看來這東西蠻強大的,開始個人探索之路吧.post
先簡單看一下它的用法先:性能
/** * Demonstration of the use of a CursorLoader to load and display contacts * data in a fragment. */ public class LoaderCursor extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FragmentManager fm = getFragmentManager(); // Create the list fragment and add it as our sole content. if (fm.findFragmentById(android.R.id.content) == null) { CursorLoaderListFragment list = new CursorLoaderListFragment(); fm.beginTransaction().add(android.R.id.content, list).commit(); } } public static class CursorLoaderListFragment extends ListFragment implements 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) { 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); getLoaderManager().initLoader(0, null, this); } @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); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } 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); } } }
這裏是Android提供的實例代碼,有刪減。this
從代碼上看來,經過實現LoaderManager.LoaderCallbacks就好了.
在onCreateLoader裏面實現你要請求的耗時操做,當異步線程操做完成以後就會從onLoadFinished返回數據.
用起來是否是很簡單呢?下面具體來看一下它是怎麼作到的吧.
getLoaderManager()是定義在Activity類的一個方法,返回類型LoaderManager,但這只是個接口,它真正的實現類是誰呢?
繼續往下走,看到這個LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create),方法時,答案便揭曉了.
下面咱們來看看LoaderManager相關的類結構,省略了不少東西,但不影響咱們的分析.
如今咱們來到了LoaderManagerImp的initLoader方法了.
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } LoaderInfo info = mLoaders.get(id); if (info == null) { // Loader doesn't already exist; create. info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); if (DEBUG) Log.v(TAG, " Created new loader " + info); } else { if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; } if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } return (Loader<D>)info.mLoader; }
這是一個新的Loader,那麼info應該是null,轉入執行createAndInstallLoader.
private LoaderInfo createAndInstallLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback) { try { mCreatingLoader = true; LoaderInfo info = createLoader(id, args, callback); installLoader(info); return info; } finally { mCreatingLoader = false; } } private LoaderInfo createLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback) { LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); Loader<Object> loader = callback.onCreateLoader(id, args); info.mLoader = (Loader<Object>)loader; return info; } void installLoader(LoaderInfo info) { mLoaders.put(info.mId, info); if (mStarted) { // The activity will start all existing loaders in it's onStart(), // so only start them here if we're past that point of the activitiy's // life cycle info.start(); } }
createLoader把必要的信息都封裝在LoaderInfo類裏面,留意如下這一行:
callback.onCreateLoader(id,arg),這裏正是咱們上面在客戶端實現接口LoaderCallback的那個方法.
接着調用installLoader,這個方法把此次Loader的信息put進mLoader這個SparseArrayCompat中,這個對象能夠理解爲一個Map,它的性能比Map要好.
mStarted的值是true,它是在getLoaderManager的時候在Activity中傳進來的true值.
好了,下面進入LoaderInfo的start方法了.
void start() { if (mLoader != null) { if (!mListenerRegistered) { mLoader.registerListener(mId, this); mListenerRegistered = true; } mLoader.startLoading(); } }
mLoader就是在客戶端實現的那個Loader,回到咱們剛開始時的例子,它就是一個CursorLoader.
在分析CursorLoader的startLoading以前,咱們先看一下這些Loader的類結構先:
從這些類的名稱看來,真正實現了異步傳輸功能的類應該就是AsyncTaskLoader了,事實是否是這樣呢?
繼續深刻下去:
這裏的startLoading是調用了Loader類的方法,下文中我會用這樣的方法來標識方法是屬於哪一個類的: 如Loader –> startLoading
Loader: public final void startLoading() { mStarted = true; mReset = false; mAbandoned = false; onStartLoading(); } CursorLoader: protected void onStartLoading() { if (mCursor != null) { deliverResult(mCursor); } if (takeContentChanged() || mCursor == null) { forceLoad(); } } AsynTaskLoader: protected void onForceLoad() { super.onForceLoad(); cancelLoad(); mTask = new LoadTask(); if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask); executePendingTask(); }
終於看到了LoadTask關鍵字啦,答案就要揭曉啦.
AsyncTaskLoader: final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable { private final CountDownLatch mDone = new CountDownLatch(1); // Set to true to indicate that the task has been posted to a handler for // execution at a later time. Used to throttle updates. boolean waiting; /* Runs on a worker thread */ @Override protected D doInBackground(Void... params) { if (DEBUG) Slog.v(TAG, this + " >>> doInBackground"); try { D data = AsyncTaskLoader.this.onLoadInBackground(); return data; } catch (OperationCanceledException ex) { } } /* Runs on the UI thread */ @Override protected void onPostExecute(D data) { if (DEBUG) Slog.v(TAG, this + " onPostExecute"); try { AsyncTaskLoader.this.dispatchOnLoadComplete(this, data); } finally { mDone.countDown(); } } } AsyncTaskLoader: protected D onLoadInBackground() { return loadInBackground(); } CursorLoader: public Cursor loadInBackground() { try { Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder, mCancellationSignal); if (cursor != null) { // Ensure the cursor window is filled cursor.getCount(); registerContentObserver(cursor, mObserver); } return cursor; } finally { synchronized (this) { mCancellationSignal = null; } }
LoadTask原來是個AsyncTask類型,看到這裏你們你們應該以爲有種豁然的感受了吧.
在ForceLoad裏面啓動該線程,開始執行doInBackground,回調CursorLoader裏面的loadInBackgroud,這個方法裏面執行真正的耗時操做,
執行完以後一層一層返回,接着調用onPostExecute方法.
好了,如今數據總算是拿到了.
接着執行,把獲取的數據往回調.
LoadTask -> onPostExecute
----->
AsynTaskLoader-> dispatchOnLoadComplete
----->
Loader->deliverResult
回調前面註冊的loadComplete:
LoaderInfo -> onLoadComplete
---->
LoaderInfo ->callOnLoadFinished
把數據回調給客戶端
mCallbacks.onLoadFinished(loader, data);
到這裏就完美解釋了Loader的特色2,異步
第三點當數據源改變時能及時通知客戶端又是如何體現的呢?
這裏用了觀察者模式來實現.咱們先看一下CursorLoader的構造函數:
mObserver = new ForceLoadContentObserver();
這個ForceLoadContentObserver是什麼東西呢?
ForceLoadContentObserver繼承了ContentObserver,這是Android內部的一個對象,繼承了它,就能享受到數據變化時能夠接收到通知(也就是觀察者中的Subject),這裏相似於數據庫中的觸發器.
先往下看:
在CursorLoader->loadInBackground方法中有這麼一句:
registerContentObserver(cursor, mObserver);//註冊觀察者
答案揭曉了.
註冊觀察者後,當對應的URI發生變化是,會觸發onChange方法
public void onChange(boolean selfChange) { onContentChanged(); } public void onContentChanged() { if (mStarted) { forceLoad(); //這裏從新發送請求. } else { // This loader has been stopped, so we don't want to load // new data right now... but keep track of it changing to // refresh later if we start again. mContentChanged = true; } }
對於forceLoad方法前面已經提升過了,你們應該還有印象吧.
最後一個問題,也就是第四點:如何作到在configuration change自動重連接的呢?
只要能回答這兩個問題,這個問題就解決了.
<1>loader如何在configuration change以前保存數據?
<2>loader如何在configuration chage以後恢復數據並繼續load?
LoaderManager:
還記得嗎?Loader建立之初,在LoaderManagerImp->installLoader方法裏面,
mLoaders.put(info.mId, info);
Info 是LoaderInfo對象,裏面封裝了Loader的相關信息,表示這個LoaderInfo的Key是mId.
就是在這裏保存了loader.這樣就回答了問題<1>
對於問題二,首先咱們來了解一下configuration change發生以後會發生什麼事情呢?
還記得這個生命週期圖嗎,Fragment的也是差很少的.
當configuration change發生以後,會先把原來的Activity銷燬掉,而後再從新構建一個,
也就是會重走一遍onCreate->onStart->onResume的過程.
好了,明白這個以後,我在onStart方法裏面找到了線索.
Activity: protected void onStart() { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); mCalled = true; if (!mLoadersStarted) { mLoadersStarted = true; if (mLoaderManager != null) { mLoaderManager.doStart(); } else if (!mCheckedForLoaderManager) { mLoaderManager = getLoaderManager(null, mLoadersStarted, false); } mCheckedForLoaderManager = true; } getApplication().dispatchActivityStarted(this); } LoaderManagerImp: void doStart() { if (DEBUG) Log.v(TAG, "Starting in " + this); if (mStarted) { RuntimeException e = new RuntimeException("here"); e.fillInStackTrace(); Log.w(TAG, "Called doStart when already started: " + this, e); return; } mStarted = true; // Call out to sub classes so they can start their loaders // Let the existing loaders know that we want to be notified when a load is complete for (int i = mLoaders.size()-1; i >= 0; i--) { mLoaders.valueAt(i).start(); } }
留意doStart的For循環,真相大白了..
最後總結一下:
一、異步是經過AsynTaskLoader來實現的。
二、經過觀察者模式來實現監控數據的變化.
三、經過Activity生命週期中的onStart來實現自動重鏈接.