Android API Guides 閱讀筆記(5)----Loader

使用Loader,是爲了方便在Activity或者Fragment中異步加載數據及監聽數據源的變化,Loader的一些特徵:

  • 每一個Activity或者Fragment都能使用Loader

  • Loader提供異步加載數據的數據

  • Loader可以監聽數據資源,有數據內容改變時Loader可以提供新的數據

  • 當配置改變導致Activity或者Fragment重新創建後,Loader可以自動連接上次的遊標(cursor),這樣就不用重新執行查詢操作了

在一個應用程序中使用Loader可能會用到的類文件(class)和接口(interface)如下:

  • LoaderManager:用來管理Loader的實例,通常和CursorLoader結合起來用,一個Activity或者Fragment中只能有一個LoaderManager,但是一個LoaderManager可以有多個Loaders

  • LoaderManager.LoaderCallbacks:用來和LoaderManager進行交互的回調接口

  • Loader:抽象類,用於執行異步加載數據,通常使用其子類CursorLoader,但也可以自定義一個子類使用

  • AsyncTaskLoader:Loader的子類,提供一個異步任務(AsyncTask)進行相關處理

  • CursorLoader:是AsycnTaskLoader的子類,用於查詢ContentResolver並且返回一個Cursor,常用於從ContentProvider中異步加載數據

一個使用Loader的應用程序的組成:

  • 在一個Activity或者Fragment中

  • 定義LoaderManager的實例

  • 定義一個CursorLoader的實例用以從ContentProvider中加載數據,或者自定義一個Loader或者AsyncTaskLoader的子類去執行這個操作

  • 實現LoaderManager.LoaderCallbacks接口,後面暫時略過。。。

  • 接下來就是展示加載到的數據了,可以使用SimpleCursorAdapter等

  • 當然,最重要的是要有數據源,比如一個ContentProvider提供的數據源

啓動一個Loader

  • 使用LoaderManager,在Activity的onCreate()或者在Fragment的onActivityCreated()方法中初始化Loader:
   getLoaderManager.initLoader(0,null,this);

initLoader中的參數如下:

  • 一個唯一識別Loader的id

  • 構造方法的可選參數

  • LoaderManager.LoaderCallbacks的實現,用以報告Loader的事件(先寫着,再改)

initLoader方法用來確保Loader初始化完成並且處於活動狀態,調用此方法的結果如下:

  • 如果通過id標識的這個Loader已經存在了,上次創建的Loader將會被重用

  • 如果指定id標識的Loader不存在,則通過LoaderManager.LoaderCallbacks的onCreateLoader()方法回調得到一個新的Loader實例

通常,Loader的生命週期由LoaderManager管理,所以我們很少直接與Loader交互

重新啓動一個Loader

使用initLoader()方法,如果一個Loader已經存在了,則會重新使用這個存在的,所以當我不想使用這個存在的,就需要重新啓動一個Loader,這時就可以使用restartLoader()方法

使用LoaderManager的回調方法

使用LoaderManager.LoaderCallbacks實現與LoaderManager的交互

Loader特別是CursorLoader,通常會在停止後仍然保留數據,這樣就可以在Activity或者Fragment的onStop()和onStart()方法中保留應用程序的數據,這樣,當用戶返回到當前應用程序時,無需等待數據重新加載(對這句話不太理解)

LoaderManager.LoaderCallbacks包含如下回調方法:

  • onCreateLoader():通過Id進行實例化並返回一個新的Loader,通常在使用initLoader()時,如果指定Id的Loader不存在,就會新建一個Loader,就會用到這個回調方法(下面的實例中會說明其返回的Cursor的組成)

  • onLoadFinished():當之前創建的Loader加載完成時調用

  • onLoaderReset():當之前創建的Loader被重置時調用,這樣,之前的Loader中的數據就不可用了

下面是官方文檔的實例,通過實例能更好的理解怎樣使用Loader:


public class CursorLoaderListFragment extends ListFragment
        implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // 這是用於顯示列表數據的Adapter
    SimpleCursorAdapter mAdapter;

    // 如果非null,這是當前的搜索過慮條件
    String mCurFilter;

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

        // 如果列表中沒有數據,就給控件一些文字去顯示.在一個真正的應用
        // 中這應用資源中取得.
        setEmptyText("No phone numbers");

        // 設置在頂部欄中有菜單項.也就是允許Fragment使用菜單,這在之前的Fragment中有筆記
        setHasOptionsMenu(true);

        // 創建一個空的adapter,將用它顯示加載後的數據
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
                new int[]{android.R.id.text1, android.R.id.text2}, 0);

        //填充ListView
        setListAdapter(mAdapter);

        // 準備loader.可能是重連到一個已存在的或開始一個新的
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // 頂部欄的菜單項,用於點擊出現搜索框
        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) {

        //獲取搜索過濾條件(也就是變化是如果輸入了文字就獲取到)
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;

        //並重啓loader來執行一個新的查詢
        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) {
        // ListView中的點擊事件,寫入你想寫的代碼
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // 這是想獲取的聯繫人中一行的數據.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.CONTACT_STATUS,
            ContactsContract.Contacts.CONTACT_PRESENCE,
            ContactsContract.Contacts.PHOTO_ID,
            ContactsContract.Contacts.LOOKUP_KEY,
    };

    /**
     * 當一個新的loader需被創建時調用.本例僅有一個Loader,所以不需要ID.首先設置baseUri,URI指向的是聯繫人
     */
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        Uri baseUri;

        //如果有搜索條件,就過濾出條件匹配的數據,如果沒有搜索條件,就直接獲取所有的數據
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        // 現在創建並返回一個CursorLoader,它將負責創建一個Cursor用於顯示數據
        String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        //CursorLoader的參數解釋:CursorLoader(Context ctx,Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
        // ctx:爲上下文,不多說
        // uri:需要搜索的內容的uri
        // projection:指定需要返回的列的集合,如果要返回所有列,使用null,但這樣效率比較低
        // selection:過濾需要返回的行,就像是SQL中的where語句後面的條件,使用null返回所有行
        // selectArgs:過濾的條件,可以在上面的selection中使用"?"隔開來設置條件,也可以在這裏寫下條件,效果是一樣的
        // sortOrder:對列進行排序,就像是SQL中的“order by”後面的內容
    }

    /**
     * 當之前創建的Loader加載完成時調用,將新的cursor換進來
     */
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        mAdapter.swapCursor(data);
    }

    /**
     * 在最後一個Cursor準備進入上面的onLoadFinished()之前,Cursor要被關閉了,需要確保不再使用它
     */
    public void onLoaderReset(Loader<Cursor> loader) {

        mAdapter.swapCursor(null);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章