天天看點

LoaderManager及Loader初步探索

轉載  http://blog.sina.com.cn/s/blog_62c5894901014g5x.html

在Android3.0中,Google引入了一種資料異步加載機制,該機制的核心,便是LoaderManager、Loader,顧名思義,LoaderManager是Loader的管理者,而Loader便是資料加載器,你可以根據自己的需要實作形形色色的資料加載器。

Google強烈建議在加載資料時,使用LoaderManager及其相關的機制。

每個Activity和Fragment中,都會有且隻有一個LoaderManager,而LoaderManager中可以有多個Loader,也就是說,在一個Activity或者Fragment中,你可以同時異步加載N則不同的資料,具體加多少則,要看你那一畝三分地(Activity和Fragment就是你的地)有多大産。

Google倒是提供了一個标準的Loader,即CursorLoader,它是Loader的标準實作,如果你的資料能夠用Cursor表示,比如來自SQLiteDatabase的資料就是标準的Cursor,那麼這個類對你而言就夠用了,具體如何使用CursorLoader,請參看如下兩個例子:

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html

這個例子中牽涉的東西較多,除了我們關注的CursorLoader外,還包括ContentProvider、SQLiteOperHelper、SQLiteDatabase、Fragment、Uri等等常用概念,是以,在仔細閱讀了本例後,你将學會如何讓這這些類在你的應用中分工協作。

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html

這個例子要簡單些,它教你如何擷取聯系人,雖然簡單,卻切中要害。

本文若是隻關注這些,就沒有意思了,畢竟在很多情況下,我們會感覺CursorLoader不順眼,于是想寫一個屬于自己的、更帥的Loader,此時抽象類AsyncTaskLoader就會叫嚣着粉墨登場了,該抽象類定義了你的加載器異步加載資料需要實作的接口,那麼如何實作呢?你可以去看下面的例子:

http://developer.android.com/reference/android/content/AsyncTaskLoader.html#loadInBackground()

為了讓你以及我自己更好的學習自定義Loader這一伎倆,我把裡頭的關鍵代碼摘了出來,咱們共同分析一番:

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();

    }

    @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;

    }

    @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 = apps;

        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);

        }

    }

    @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();

        }

    }

    @Override protected void onStopLoading() {

        // Attempt to cancel the current load task if possible.

        cancelLoad();

    }

    @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);

    }

    @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;

        }

    }

    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.

    }

}

諸位請看loadInBackground方法,這是Loader的核心方法,必須得重載,你要在這裡頭實作加載資料的功能,看看名字就知道,該方法将在背景運作,這方法沒有什麼特别說的,自己想加什麼樣的資料,自己清楚。  

再看deliverResult方法,當資料到達用戶端後,這個方法将被調用,該方法可以不重載,你可以在其中根據需要實作傳遞資料的邏輯,請注意例子中對兩個狀态的判斷(注:程式設計,就得養成if的好習慣),一個是isReset()這個方法用來判斷Loader是否已經被重置,如果重置了,那麼留着資源也沒有啥用了,得把它釋放掉;一個是isStarted(),如果Loader被啟動那麼就把資料傳遞出去:直接調super的傳遞方法就OK。

onStartLoading方法,必須得重載,且别忘記在裡頭調用forceLoad方法,你也看到,在例子中人家調用了deliverResult方法,并且建立了一個觀察者來接收資料,在調用forceLoad時,請注意相應的條件判斷,關于為什麼要調用forceLoad方法,網上有下面一段話:

onStartLoading() is called in two places:

1. LoaderManagerImpl.doStart()->LoaderInfo.start(), which will happen when the hosting fragment isstarted.

2. LoaderManagerImpl.installLoader(LoaderInfo)->if the hosting fragment is started, callLoaderInfo.start().

So basically, your custom class extending AsyncTaskLoader will have to override onStartLoading(),and call forceLoad() within when necessary.

Loader::startLoading() will be called when the hosting fragment is started, and youronStartLoading() will be called as consequence.

    以我目前的水準,并不能确切明白這段話的含義是什麼,但是,我翻了一下Android SDK的源代碼,發現Loader及AsyncTaskLoader中,startLoading方法其實屁都沒做,是以,萬事求己不如求人,還是老老實實去實作onStartLoading方法吧,不過Google這麼做有些坑爹的嫌疑,是以很多人在那裡問,哎呀,我的loadInBackground怎麼老不執行啊?

    onStopLoading、onCancel方法都沒有什麼好說的,倒是onReset方法的實作值得關注,諸位看看便知,至于onReleaseResouces方法,就是給你一次釋放資源的機會,如果你用的是Cursor之類的東西,請在這裡頭close吧。

    我剛剛開始研究Android SDK,也許是從.NET猛地轉到JAVA的緣故,很多架構 方面的東西都不是很習慣。Google Android SDK的架構簡單、靈活而又強大,這是值得稱道的,但是也有其不甚完善的地方。就拿LoaderManager來說,知道Android 3.0才引入,Google開發文檔說,使用這東西,能夠避免在加載資料時界面死在那裡。于是,我就相當然地認為,Google不是神,它的産品設計中,也會存在哪些低級的毗漏與缺陷。

但是,無論如何Google Android SDK以及iOS SDK,都非常值得我們去研究一番,即便你不程式設計,不寫代碼,但是裡頭的思想,确是一筆财富。