Android之Loader理解

在看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來實現自動重鏈接.

相關文章
相關標籤/搜索