Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲

Loaders

編輯:關於Android編程

Loaders

從Android 3.0開始引進了loader(加載器)技術, 在activity或者fragment中,loaders可以把異步地加載數據變得更簡單。Loaders具有以下特性:

他們對於每一個Activity和Fragment都是有效的。 他們可以提供異步加載數據的能力。 他們監視數據源,並當內容改變時傳遞當前最新的結果。 當他們因為配置的改變而重新連接的時候,他們會自動地重連到上一個loader的游標。因此,他們不需要重新查詢數據。

在應用中使用loaders可能會涉及到多個類和接口。他們被匯總到了下表中:

類/接口 描述 LoaderManager 一個關聯到Activity或 Fragment的抽象類,用來管理一個或多個 Loader實例這樣可以幫助一個應用管理那些跟Activity 或者Fragment的生命周期聯系到一起的長時間運行的操作;這樣的最常見的使用就是 CursorLoader,然而應用可以自由地寫自己的加載器來加載其他類型的數據。

每一個activity或者fragment都只有一個LoaderManager。但是一個 LoaderManager可以擁有多個加載器(loaders)。
LoaderManager.LoaderCallbacks 一個用來去為客戶端和LoaderManager提供交互的回調接口。 舉個例子,你可以使用onCreateLoader() 回調方法來創建一個新的loader(加載器)。 Loader 一個執行異步的數據加載的抽象類。這是加載器的基類。你可以使用典型的CursorLoader,但是也可以實現你自己的子類。 當loaders被激活的時候,它們應該見識數據源並且當內容改變的時候傳遞最新結果。 AsyncTaskLoader 提供一個AsyncTask來執行異步加載數據的抽象loader。 CursorLoader AsyncTaskLoader的一個子類,查詢 ContentResolver然後返回一個Cursor。這個類用標准的方式實現了 Loader的協議以此來查詢cursors, AsyncTaskLoader在後台線程中執行cursor查詢所以它不會阻塞應用的UI。 使用loader是從ContentProvider異步加載數據的最好的方式, 相對於通過fragment或activity的API來執行查詢

上表中的那些類和接口都是你將會用來在應用中實現loader的極其重要的組件。你不必在創建每一個loader的時候全部使用,但是你總是需要一個 LoaderManager的引用,用來初始化一個loader Loader類的實現,類似於CursorLoader。 下面的章節將會向您展示怎樣在應用中使用這些類和接口。

在應用中使用Loader

一個典型地使用了loaders的應用應該包含以下內容:

一個Activity或者Fragment。 一個LoaderManager的實例。 一個CursorLoader來載入被ContentProvider返回的數據。 或者,你可以實現自己的Loader或者AsyncTaskLoader的子類 從而從其他的資源加載數據 LoaderManager.LoaderCallbacks的一個實現 這是你創建新的loader和管理你的已經存在的loader的引用的地方 一個展示loader的數據的方法,比如一個SimpleCursorAdapter。 一個數據源,比如一個ContentProvider,當你使用一個 CursorLoader的時候。

開始使用Loader

LoaderManager管理一個Activity或 Fragment范圍內的一個或多個Loader實例。每一個Activity或者Fragment都只有一個 LoaderManager。

你可以典型地初始化一個 Loader,可以在activity的 onCreate()方法,也可以在fragment的 onActivityCreated()方法中。你可以像下面一樣做這件事:

//准備loader,無論是與一個已經存在的loader重連,
//還是新建一個。
getLoaderManager().initLoader(0, null, this);

initLoader()方法需要以下參數:

一個可以標識loader的唯一的ID。本例中的ID是0。 一個可選參數,當loader初始化時提供給它(在本例中是null)。 一個LoaderManager.LoaderCallbacks的實現,將會被 LoaderManager調用,用來報告loader的時間。本例中,本地類實現了 LoaderManager.LoaderCallbacks借口,所以它傳遞了一個自身的引用 this。

initLoader()方法調用確保了一個loader會被初始化以及激活 它有兩種可能的後果:

如果賦予loader的ID已經存在,那麼上一個被創建的loader就會被重用 如果賦予loader的ID不存在, initLoader()就會觸發 LoaderManager.LoaderCallbacks的onCreateLoader()方法。 這裡就是你實例化並返回一個新loader的地方。 更多的討論,請參看onCreateLoader章節。

無論在哪一種情況中,傳入的LoaderManager.LoaderCallbacks 實現都會跟loader綁定在一起,它將會在loader狀態改變時被調用。如果在本次調用時,調用者處於開始狀態,並且所請求的loader已經存在並產生了數據,那麼系統就會立馬調用 onLoadFinished() (即在initLoader()過程中), 所以你必須為這種情況做好准備。更多關於這個回調方法的討論,請參看 onLoadFinished。

請注意,initLoader() 方法返回被創建的Loader,但是你不必保留它的引用, LoaderManager會自動管理loader的生命, LoaderManager 會在必要的時候啟動和終止,以及維護loader的狀態和它關聯的內容。這就意味著,你幾乎不用和loader進行直接交互 (尋求使用loader方法來調整loader行為的例子,請參看 LoaderThrottle實例)。 你最常用的手段是當特定事件發生時,使用LoaderManager.LoaderCallbacks方法來介入到加載過程中。 請參看Using the LoaderManager Callbacks。

重啟一個Loader

當你像上面展示的那樣使用initLoader()的時候, 如果有的話,它會使用已存在的帶有標識ID的loader。 如果沒有,它會創建一個。但是有時你會想要丟掉舊的數據,開始新的過程。

為了丟棄舊的數據,你要使用restartLoader()。 例如,SearchView.OnQueryTextListener的實現在用戶查詢改變時重啟了, loader需要被重啟從而能夠使用修改過的搜索過濾進行新的查詢:

public boolean onQueryTextChanged(String newText) {
    //當action bar的搜索文本改變時調用。
    //更新搜索過濾器,然後重啟loader用當前的過濾器
    //做一次新的查詢。
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

使用LoaderManager回調

LoaderManager.LoaderCallbacks是一個回調接口,它為客戶端提供與 is a callback interface LoaderManager交互的能力。

Loaders,尤其是CursorLoader,大家都希望當它被停止以後仍然可以保持數據。 這樣允許應用在activity或fragment的 onStop()和onStart()方法之間保持數據, 以至於當用戶返回到一個應用的時候,他們不必再等待數據的重新加載。 你可以使用LoaderManager.LoaderCallbacks方法 當你知道何時需要創建新的loader,以及高速應用何時停止使用loader的數據。

LoaderManager.LoaderCallbacks包括以下方法:

onCreateLoader() — 根據傳入的ID初始化並返回一個新的Loader。 onLoadFinished() — 當一個之前被創建的loader已經結束加載數據的時候會調用此方法。 onLoaderReset() — 當一個之前創建的loader被重置的時候會調用此方法,這樣會導致它的數據不可用。

在下面的章節中會更詳細地描述這些方法的細節

onCreateLoader

當你試圖操作一個loader的時候,(例如通過initLoader()), 會檢查被賦予唯一ID的loader是否存在。如果不存在,它會觸發LoaderManager.LoaderCallbacks的onCreateLoader()方法。 這是你創建新loader的地方。一般來說被創建的都是CursorLoader,但是你可以實現你自己的Loader子類。
在本例中,onCreateLoader() 回調方法創建了一個CursorLoader。你必須使用它的構造方法建造這個 CursorLoader,構造方法需要向 which ContentProvider執行一次查詢的完整信息作為參數。它還需要:尤其地,它還需要:
* uri — 要獲取的內容的URI。
* projection — 返回的列組成的列表,傳入null將會返回所有列,但是效率很低。
* selection — 一個聲明返回哪些行的過濾器,被格式化成類似SQL中WHERE子句的形式(除了沒有WHERE自己)。傳入null將會返回給定URI的所有行。
* selectionArgs — 你可能在Selection中包含一些‘?’,他們將會被selectionArgs的值給替換掉,順序與它們在selection中出現的順序一致。 這些值被約束為String類型
* sortOrder — 怎樣給這些行排順序,被格式化為類似SQL中ORDER BY子句的形式(除了沒有ORDER自己)。傳入null 將會使用默認的排序方式,可能是沒有順序。

例如:

//如果不是null,這就是當前的用戶提供的過濾器。
String mCurFilter;
...
public Loader onCreateLoader(int id, Bundle args) {
    //當新的Loader需要被創建的時候調用此方法。
    //本例僅有一個Loader,所以不必關心ID的問題。
    //首先,根據我們是否正在過濾,
    //選擇base URI來使用。
    Uri baseUri;
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    //現在創建並返回一個CursorLoader,
    //它會創建一個用來顯示數據的Cursor。
    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");
}

onLoadFinished

當上一個被創建的loader已經結束數據加載的時候調用此方法。這個方法被保證會在提供給這個loader的數據被釋放之前調用。 這個時候,你應該移除所有舊數據的使用(因為它們馬上就會被釋放),但是不應該自己去釋放它們,因為它們的loader會做這些事。

一旦知道了應用將不會再使用這些數據,loader就應該立即釋放它們。 例如,數據是來自CursorLoader的一個cursor, 你就不應該再調用close()。如果這個cursor正要在 CursorAdapter中被替換,你應該使用swapCursor()方法使得舊的 Cursor不被關閉。例如:

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

public void onLoadFinished(Loader loader, Cursor data) {
    //把新cursor換進來  (一旦我們返回了,框架將會管理
    //關閉舊cursor的事情)
    mAdapter.swapCursor(data);
}

onLoaderReset

當之前創建的loader被重置使得數據不可用的時候,此方法被調用。這個回調方法讓你弄清楚數據何時會被釋放,進而你可以移除對它的引用

下面的實現調用了參數為null的 swapCursor() :

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

public void onLoaderReset(Loader loader) {
    //當最後一個Cursor進入到onLoadFinished()時被調用,
    //Cursor將要被關閉, 我們要確保
    //不會再使用到它。
    mAdapter.swapCursor(null);
}

Example

下面的例子完整實現了一個Fragment顯示一個包含了從聯系人content provider返回的查詢數據的ListView的內容的功能。 它使用一個CursorLoader來管理對provider的查詢。
一個應用想要實現操作用戶的聯系人,如例子中那樣,它的manifest一定要包含 READ_CONTACTS權限。

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

    //這就是用來展示列表信息的Adapter。
    SimpleCursorAdapter mAdapter;

    //如果不是null,這就是當前的搜索過濾器。
    String mCurFilter;

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

        //如果沒有數據,就給控件一些文本去顯示。  
        //在真正的應用中,信息來自應用資源。
        setEmptyText("No phone numbers");

        //我們在action bar中顯示一個菜單項。
        setHasOptionsMenu(true);

        //創建一個新的adapter,我們將用它來顯示加載的數據。
        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);

        //准備loader, 重連到一個已存在的loader,
        //或者啟動一個新的loader。
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        //放置一個action bar用於搜索。
        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) {
        //action bar上的搜索文本改變的時候被調用。
        //更新搜索過濾器,並且重啟loader用當前的過濾器
        //來做新的查詢。
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        //不必關心這個方法。
        return true;
    }

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

    //我們想獲取的聯系人中的行數據。
    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 onCreateLoader(int id, Bundle args) {
        //當需要創建一個新的loader時被調用。
        //本例中僅有一個loader,所以我們不必關心ID的問題。
        //首先,根據我們當前是否正在過濾,
        //選擇base URI來使用。
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        //現在創建並返回一個CursorLoader,
        //它將會為被顯示的數據創建一個Cursor。
        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 loader, Cursor data) {
        //把新的cursor換進來。  (框架將會在我們返回的時候
        //管理舊cursor的關閉事宜。)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader loader) {
        //當最後一個Cursor進入onLoadFinished()的時候被調用。
        //cursor將要被關閉, 我們應該確保
        //不再使用它。
        mAdapter.swapCursor(null);
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved