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