lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

載入器

在 Android 3.0 導入的載入器可輕鬆在 Activity 或片段中,以非同步方式載入資料。 載入器的特性如下:

  • 適用於所有 ActivityFragment
  • 提供以非同步方式載入資料。
  • 監視其資料來源,並在內容變更時傳送新的結果。
  • 可在設定變更後重新建立時,自動重新連接到上次載入器的游標, 因此不需要重新查詢資料。

載入器 API 摘要

有多個類別和介面可能與在應用程式中使用載入器有關。 請參閱下表的摘要說明:

類別/介面 說明
LoaderManager ActivityFragment 相關聯的抽象類別,可用於管理一或多個 Loader 執行個體。這可以協助應用程式管理所需執行時間較長的操作與 ActivityFragment 生命週期搭配使用,這最常與 CursorLoader 搭配使用,不過應用程式能夠撰寫自己的載入器來載入其他類型的資料。

每個 Activity 或片段只能有一個 LoaderManager,但 LoaderManager 可以有多個載入器。
LoaderManager.LoaderCallbacks 可供用戶端與 LoaderManager 互動的回呼介面。例如,您使用 onCreateLoader() 回呼方法來建立新的載入器。
Loader 以非同步方式載入資料的抽象類別。這是載入器的基本類別。 您通常會使用 CursorLoader,但您也可以實作自己的子類別。載入器處於使用中時,應會監視其資料來源,並在內容有變更時傳送新的結果。
AsyncTaskLoader 提供 AsyncTask 以執行工作的抽象載入器。
CursorLoader 查詢 ContentResolver 並傳回 CursorAsyncTaskLoader 子類別。此類別會以標準方式實作 Loader 通訊協定,用來查詢建置於 AsyncTaskLoader 的游標,以便在背景執行緒中執行游標查詢,藉此避免封鎖應用程式的 UI。 以非同步方式從 ContentProvider 載入資料,而不是透過片段或 Activity 的 API 來管理查詢,最好的方法就是使用此載入器。

上表中的類別和介面就是您將用來在應用程式中實作載入器的基本元件。 上述元件不需要在您建立載入器時全部使用,但您必須一律參照到 LoaderManager,才能初始化載入器並實作 Loader 類別,例如 CursorLoader。 以下各節說明如何在應用程式中使用這些類別和介面。

在應用程式中使用載入器

本節說明如何在 Android 應用程式中使用載入器。使用載入器的應用程式通常包括下列物件:

啟動載入器

LoaderManager 可在 ActivityFragment 內管理一或多個 Loader 執行個體。 每個 Activity 或片段只能有一個 LoaderManager

您通常會在 Activity 的 onCreate() 方法內或在片段的 onActivityCreated() 方法內,初始化 Loader, 如下所示:

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

initLoader() 方法採用下列參數:

  • 可識別載入器的不重複 ID。在本範例中,此 ID 為 0。
  • 可在建構時提供給載入器的選用引數 (在本範例中為null)。
  • LoaderManager.LoaderCallbacks 實作, LoaderManager 會呼叫此實作來回報載入器事件。在本範例中,本機類別會實作 LoaderManager.LoaderCallbacks 執行個體,以將參照傳送給它自己 (this)。

initLoader() 呼叫可確保載入器已初始化且處於有效狀態。 可能會有兩種結果:

在任一情況下,指定的 LoaderManager.LoaderCallbacks實作會與載入器建立關聯且會在載入器狀態變更時呼叫。 如果進行此呼叫時,呼叫器處於已啟動狀態,而要求的載入器已經存在並產生自己的資料,那麼系統會立即呼叫 onLoadFinished() (在 initLoader() 期間),請務必做好發生這種情況的準備。 如要進一步瞭解此回呼,請參閱 onLoadFinished

請注意,initLoader()方法會傳回建立的 Loader,但您不需要擷取它的參照。 LoaderManager 會自動管理載入器的生命週期。 LoaderManager 會在必要時啟動及停止載入,並維護載入器的狀態與其相關內容。 顧名思義,您鮮少會直接與載入器互動 (但如需使用載入器方法微調載入器行為的範例,請參閱 LoaderThrottle 範例)。 當發生特定事件時,您最常會使用 LoaderManager.LoaderCallbacks 方法來介入載入程序。 如要進一步瞭解此主題,請參閱使用 LoaderManager 回呼

重新啟動載入器

當您使用 initLoader() (如上所示),它會使用已指定 ID 的現有載入器 (如果有的話)。 如果沒有,就會自行建立載入器。不過,有時候您會想捨棄舊資料並從頭開始。

如要捨棄舊資料,請使用 restartLoader()。例如,當使用者的查詢改變時,實作 SearchView.OnQueryTextListener 會重新啟動載入器。 您必須重新啟動載入器,才能使用修訂後的搜尋篩選器執行新的查詢:

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

使用 LoaderManager 回呼

LoaderManager.LoaderCallbacks 是回呼介面,可讓用戶端與 LoaderManager 互動。

載入器 (特別是 CursorLoader) 在停止之後,應該會保留它們的資料。 這可讓應用程式保留其在 Activity 或片段的 onStop()onStart() 方法間的資料,好讓使用者回到應用程式時,不必枯等資料重新載入。 您可以使用 LoaderManager.LoaderCallbacks 方法,藉以得知何時該建立新的載入器,以及指示應用程式何時該停止使用載入器的資料。

LoaderManager.LoaderCallbacks 包含以下方法:

  • onLoadFinished() — 當先前建立的載入器已完成其載入工作時呼叫。
  • onLoaderReset() — 正要重設先前建立的載入器時呼叫,使其資料無法使用。

以上方法在下列幾節有更詳細的說明。

onCreateLoader

當您嘗試存取載入器 (例如,透過 initLoader()) 時,系統會檢查根據 ID 指定的載入器是否已存在。 如果不存在,就會觸發 LoaderManager.LoaderCallbacks 方法 onCreateLoader()。您可以在這裡建立新的載入器。 這通常會是 CursorLoader,但您也可以實作自己的 Loader 子類別。

在此範例中,onCreateLoader() 回呼方法會建立 CursorLoader。 您必須使用其建構函式方法來建置 CursorLoader,其需要一組完整的資訊才能對 ContentProvider 執行查詢。 具體來說,建構函式方法需要以下項目:

  • uri - 要擷取內容的 URI。
  • projection - 要傳回的欄清單。傳送 null 將會傳回無效的所有欄。
  • selection - 篩選器會採用 SQL WHERE 子句的格式 (WHERE 本身除外) 宣告要傳回的列。 傳送 null 將會傳回指定 URI 的所有列。
  • selectionArgs - 您可能會在選取項目中包含 ?s,而其會由來自 selectionArgs 的值按照其出現在選取項目中的順序所取代。 值將會繫結為字串。
  • sortOrder - 如何採用 SQL ORDER BY 子句的格式 (ORDER BY 本身除外) 來排列各列的順序。 傳遞 null 將會使用預設的排序順序,其可能是無排序順序。

例如:

 // 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");
}

onLoadFinished

這個方法是在先前建立的載入器已完成其載入工作時呼叫。 此方法一律是在提供給此載入器的最後資料發佈之前呼叫。 此時,您應要移除所有使用的舊資料 (由於資料即將發佈),但不應自行發佈,因為載入器擁有該資料且將會負責處理。

一旦應用程式不再使用資料時,載入器就會立即發佈資料。 例如,如果資料是來自 CursorLoader 的游標,您不應該自行對它呼叫 close()。如果正要將該游標放入 CursorAdapter,您應該使用 swapCursor() 方法,如此才不會關閉舊的 Cursor。例如:

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

onLoaderReset

這個方法是在正要重設先前建立的載入器時呼叫,以便使其資料無法使用。 此回呼方法可讓您知道即將要發佈資料,而能先行將其參照移除。  

此實作會以 null 的值呼叫 swapCursor()

// 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 管理對供應程式的查詢。

如本範例所示,針對要存取使用者聯絡人的應用程式,它的宣示說明必須包含 READ_CONTACTS 權限。

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

其他範例

ApiDemos 中有數個不同的範例,示範如何使用載入器:

  • LoaderCursor - 上述程式碼片段的完整版本在此。
  • LoaderThrottle - 以範例說明如何使用節流功能在其資料變更時降低內容供應程式執行的查詢數目。

如要進一步瞭解如何下載及安裝 SDK 範例,請參閱取得範例