Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Day33-Android新的加載模式-Loader

Day33-Android新的加載模式-Loader

編輯:關於Android編程

Android 3.0 中引入了加載器,支持輕松在 Activity 或片段中異步加載數據。 加載器具有以下特征:

可用於每個 Activity 和 Fragment。
支持異步加載數據。 監控其數據源並在內容變化時傳遞新結果。 在某一配置更改後重建加載器時,會自動重新連接上一個加載器的 Cursor。 因此,它們無需重新查詢其數據

Loader API 摘要

在應用中使用加載器時,可能會涉及到多個類和接口。 下表匯總了這些類和接口:

類/接口 描述 LoaderManager 一種與 Activity 或 Fragment 相關聯的的抽象類,用於管理一個或多個 Loader 實例。 這有助於應用管理與 Activity 或 Fragment 生命周期相關聯的、運行時間較長的操作。它最常見的用法是與 CursorLoader 一起使用,但應用可自由寫入其自己的加載器,用於加載其他類型的數據。 每個 Activity 或片段中只有一個 LoaderManager。但一個 LoaderManager 可以有多個加載器。 LoaderManager.LoaderCallbacks 一種回調接口,用於客戶端與 LoaderManager 進行交互。例如,您可使用 onCreateLoader() 回調方法創建新的加載器。 Loader 一種執行異步數據加載的抽象類。這是加載器的基類。 您通常會使用 CursorLoader,但您也可以實現自己的子類。加載器處於Activity狀態時,應監控其數據源並在內容變化時傳遞新結果。 AsyncTaskLoader 提供 AsyncTask 來執行工作的抽象加載器。 CursorLoader AsyncTaskLoader 的子類,它將查詢 ContentResolver 並返回一個 Cursor。此類采用標准方式為查詢 Cursor 實現 Loader 協議。它是以 AsyncTaskLoader 為基礎而構建,在後台線程中執行 Cursor 查詢,因此不會阻塞應用的 UI。使用此加載器是從 ContentProvider 異步加載數據的最佳方式,而不用通過片段或 Activity 的 API 來執行托管查詢。

上表中的類和接口是您在應用中用於實現加載器的基本組件。 並非您創建的每個加載器都要用到上述所有類和接口。但是,為了初始化加載器以及實現一個 Loader 類(如 CursorLoader),您始終需要要引用 LoaderManager。下文將為您展示如何在應用中使用這些類和接口。

在應用中使用加載器

此部分描述如何在 Android 應用中使用加載器。使用加載器的應用通常包括:

Activity 或 Fragment。 LoaderManager 的實例。 一個 CursorLoader,用於加載由 ContentProvider 支持的數據。您也可以實現自己的 Loader 或 AsyncTaskLoader 子類,從其他源中加載數據。 一個 LoaderManager.LoaderCallbacks 實現。您可以使用它來創建新加載器,並管理對現有加載器的引用。 一種顯示加載器數據的方法,如 SimpleCursorAdapter。 使用 CursorLoader 時的數據源,如 ContentProvider。

啟動加載器

LoaderManager 可在 Activity 或 Fragment 內管理一個或多個 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() 調用確保加載器已初始化且處於Activity狀態。這可能會出現兩種結果:

如果 ID 指定的加載器已存在,則將重復使用上次創建的加載器。 如果 ID 指定的加載器不存在,則 initLoader() 將觸發 LoaderManager.LoaderCallbacks 方法 onCreateLoader()。在此方法中,您可以實現代碼以實例化並返回新加載器。有關詳細介紹,請參閱 onCreateLoader 部分。

無論何種情況,給定的 LoaderManager.LoaderCallbacks 實現均與加載器相關聯,且將在加載器狀態變化時調用。如果在調用時,調用程序處於啟動狀態,且請求的加載器已存在並生成了數據,則系統將立即調用 LoaderManager.LoaderCallbacks#onLoadFinished 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 包括以下方法:

onCreateLoader():針對指定的 ID 進行實例化並返回新的 Loader 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:您可以在 selection 中包含 ?s,它將按照在 selection 中顯示的順序替換為 selectionArgs 中的值。該值將綁定為字串符 sortOrder:行的排序依據,其格式為 SQL ORDER BY 子句(ORDER BY 自身除外)。傳遞 null 時,將使用默認排序順序(可能並未排序)

例如:

 // If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader 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 的一個 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) {
    // 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 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 {

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

實現自定義的Loader

自定義Loader用來加載特定的數據,不局限於數據庫,可以加載文件、網絡、…

自定義的Loader 不需要開發人員手動管理 Fragment/Activity的生命周期,Loader會自己根據生命周期進行創建、加載、釋放操作。

Loader 實際上只能夠用於數據的獲取。

Loader實現時:loadInBackground() 用於數據後台加載(可以認為子線程)

步驟 :

AsyncTaskLoader : API 作用,一個抽象的,能夠在子線程中進行數據加載的Loader, 需要開發人員實現loadInBackground()

自定義的AsyncTaskLoader 必須要重寫一個方法,這個方法叫做 onStartLoading(), 在onStartLoading方法中,強制調用 forceLoad() 可以進行數據的自動加載;

代碼:


/**
 * Created with IntelliJ IDEA.
 * User: vhly[FR]
 * Date: 16/9/10
 * Email: [email protected]
 */

/**
 * 1. 繼承AsyncTaskLoader,確認好 返回的數據類型 范型就是結果類型 doInBackground 返回值
 */
public class NetworkLoader extends AsyncTaskLoader {

    private String mUrl;

    /**
     * 在構造中傳遞各種參數
     *
     * @param context
     * @param url
     */
    public NetworkLoader(Context context, String url) {
        super(context);
        mUrl = url;
    }


    @Override
    protected void onStartLoading() {
        // 這個方法必須要重寫,如果沒有重寫,數據不會自動加載
        // forceLoad 會強制觸發異步任務的加載,執行 loadInBackground()
        forceLoad();
    }

    /**
     * 子線程加載數據的部分,
     *
     * @return
     */
    @Override
    public JSONObject loadInBackground() {

        JSONObject ret = null;

        byte[] data = HttpTool.doGet(mUrl);

        //Log.d("NetworkLoader", "get data " + data.length);

        // 網絡鏈接完成之後,考慮,cancel, reset, abort 各種情況的處理

        if (isLoadInBackgroundCanceled()) {
            // 不處理,不返回
        } else {

            if (isReset()) {
                // 不返回數據,但是可以考慮,把數據保存到本地
            } else {

                if (data != null) {
                    try {
                        String str = new String(data, "UTF-8");
                        ret = new JSONObject(str);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return ret;
    }
}

重要的圖:

Loader數據加載原理:

這裡寫圖片描述

Note

Loader 的使用

1. 創建Loader,使用LoaderManager類 initLoader 方法來初始化

2. 接口方法 onCreateLoader 來創建實際的Loader對象,Android中提供了 CursorLoader 加載內容提供者的

    AsyncTaskLoader 這種Loader,是需要重寫的,自定義Loader

3. Activity onResume時,Loader開始加載數據,完成時調用 onLoadFinished 方法

4. Activity onDestroy時,Loader 會進行銷毀,釋放數據 onLoaderReset


Loader的綜合使用

1. 網絡請求 Loader 類,請求數據

2. 數據回來,生成實體類,保存到 ContentProvider

3. 網絡數據,刷新ListView

4. 程序啟動,初始化一個數據緩存Loader 也就是  CursorLoader

5. CursorLoader 加載內容提供者的數據,顯示到ListView上;

6. 本地數據庫加載完成才會啟動網絡Loader,更好處理
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved