PS:對(duì)于Loader并不是很常用,但是在google的mvp示例中有使用loader加載數(shù)據(jù)的demo,本人覺(jué)得Loader有一定的嘗試價(jià)值,v4有提供低版本支持不用擔(dān)心API版本兼容問(wèn)題。
前言
Loader API讓你從ContentProvider或者其他數(shù)據(jù)源讀取數(shù)據(jù)展示在FragmentActivity或者Fragment上。如果你不理解你為什么要用Loader API執(zhí)行看似平凡的操作,那么首先要考慮沒(méi)有l(wèi)oader的情況下可能遇到這些問(wèn)題:
- 如果你直接在activity或fragment中獲取數(shù)據(jù),由于在UI線程執(zhí)行查詢過(guò)慢,用戶將遇到缺乏響應(yīng)。
- 如果你從另一個(gè)線程獲取數(shù)據(jù),也許是AsyncTask,那么你將負(fù)責(zé)通過(guò)各個(gè)activity或fragment生命周期
管理線程和UI線程,例如onDestroy()和configurations變更。
Loader解決了這些問(wèn)題,包括其他好處。例如:
- Loader單獨(dú)運(yùn)行在隔離的線程放置janky或UI無(wú)響應(yīng)
- 當(dāng)事件發(fā)生時(shí),Loader提供回調(diào)方法簡(jiǎn)化線程管理
- 當(dāng)configuration變更時(shí),Loader保留并緩存結(jié)果,防止重復(fù)查詢
- Loader能實(shí)現(xiàn)一個(gè)Oberver監(jiān)聽(tīng)底層數(shù)據(jù)變化。例如,CursorLoader自動(dòng)注冊(cè)一個(gè)ContentObserver,當(dāng)數(shù)據(jù)變化時(shí)觸發(fā)重新加載
Loader API摘要
在app中使用Loader是可能涉及多個(gè)類和接口。
LoaderManager
一個(gè)與FragmentActivity和Fragment相關(guān)的抽象類,管理一個(gè)或多個(gè)Loader實(shí)例。 每個(gè)activity或fragment中只有一個(gè)LoaderManager,但是一個(gè)LoaderManager能管理多個(gè)Loader。
Loader調(diào)用initLoader()或restartLoader()開(kāi)始讀取數(shù)據(jù)。系統(tǒng)自動(dòng)確定具有相同ID的Loader是否存在,并將創(chuàng)建新的Loader或者重新使用現(xiàn)用的Loader。
LoaderManager.LoaderCallbacks
這個(gè)接口包含回調(diào)Loader事件觸發(fā)時(shí)回調(diào)的方法。接口定義三個(gè)回調(diào)方法:
- onCreateLoader(int,Bundle) - 當(dāng)需要一個(gè)新Loader被創(chuàng)建時(shí)調(diào)用此方法。代碼應(yīng)該創(chuàng)建一個(gè)Loader對(duì)象并返回給系統(tǒng)。
- onLoadFinished(Loader<D>,D) - 當(dāng)Loader加載數(shù)據(jù)完成后調(diào)用此方法。通常,應(yīng)該展示數(shù)據(jù)給用戶。
- onLoaderReset(Loader<D>) - 當(dāng)一個(gè)已經(jīng)創(chuàng)建的Loader被重置是(當(dāng)你調(diào)用destroyLoader(int))或activity(或fragment)已經(jīng)銷毀時(shí)調(diào)用此方法。 代碼應(yīng)該移除任何對(duì)Loader的數(shù)據(jù)的引用。
此接口通常通過(guò)activity或fragment實(shí)現(xiàn)并注冊(cè),當(dāng)調(diào)用initLoader()或restartLoader()。
Loader
Loader執(zhí)行加載數(shù)據(jù)。該類是一個(gè)抽象的,作為基礎(chǔ)類服務(wù)與所有Loader。你可以直接使用子類Loeader或者使用內(nèi)置子類之一來(lái)簡(jiǎn)單實(shí)現(xiàn)。
- AsyncTaskLoader - 一個(gè)抽象Loader,提供一個(gè)AsyncTask在單獨(dú)的線程執(zhí)行加載操作。
- Cursoroader - 一個(gè)AsyncTaskLoader用于異步加載來(lái)自ContentProvider的數(shù)據(jù)。它請(qǐng)求一個(gè)ContentResolver并返回一個(gè)Cursor。
應(yīng)用中使用Loader
app使用Loader通常包括以下內(nèi)容:
- FragmentActiivty或fragment
- 一個(gè)LoaderManager實(shí)例
- 一個(gè)CursorLoader通過(guò)ContentProvider加載數(shù)據(jù)?;蛘?,你可以實(shí)現(xiàn)你的子類Loader或AsyncTaskLoader加載其他來(lái)源的數(shù)據(jù)。
- 一個(gè)LoaderManager.LoaderCallbacks的實(shí)現(xiàn)。在你創(chuàng)建一個(gè)新Loader和管理已經(jīng)存在Loader引用的地方。
- 一個(gè)數(shù)據(jù)源,例如ContentProvider,當(dāng)使用一個(gè)CursorLoader
開(kāi)啟Loader
一個(gè)FragmentActivity或Fragmentyou只有一個(gè)LoaderManager管理一個(gè)或多個(gè)Loader實(shí)例。通常在Activity的onCreate()方法或者在fragment的onActivityCreateed()方法中初始化Loader,如下:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getSupportLoaderManager().initLoader(0, null, this);
initLoader()方法參數(shù)如下:
- 標(biāo)識(shí)Loader的唯一的ID,這里ID是0
- 提供給Loader的可選參數(shù),這里傳null
- LoaderManager.LoaderCallbacks的實(shí)現(xiàn)對(duì)象,LoaderManager會(huì)調(diào)用報(bào)告給Loader事件
initLoader()調(diào)用確保Loader被初始化和活躍。他有兩種可能:
- 如果ID指定的Loader已經(jīng)存在,則使用最后創(chuàng)建的Loader
- 如果ID指定的Loader不存在,initLoader()觸發(fā)LoaderManager.LoaderCallbacks的onCreateLoader()方法。這是實(shí)現(xiàn)代碼來(lái)實(shí)例化并返回Loader的地方。
在任何一種情況下,給的LoaderManager.LoaderCallbacks實(shí)現(xiàn)都與Loader相關(guān)聯(lián),在Loader狀態(tài)改變時(shí)被調(diào)用。如果調(diào)用的時(shí)候調(diào)用者處于開(kāi)始狀態(tài),請(qǐng)求已經(jīng)存在的Loader并生成了它的數(shù)據(jù),系統(tǒng)會(huì)立馬調(diào)用onLoadFinished()方法(在iniLoader()之間),你必須準(zhǔn)備好這些的發(fā)生。
注意initLoader()方法返回的被創(chuàng)建Loader,但是你不需要捕獲它的引用。Loadermanager自動(dòng)管理Loader的生命周期。必要時(shí)LoaderManager開(kāi)始和停止加載數(shù)據(jù),可以維持Loader相關(guān)內(nèi)容的狀態(tài)。這意味著,你很少和Loader直接交互(盡管使用Loader方法來(lái)微調(diào)Loader的行為)。
重啟Loader
當(dāng)你使用initLoader(),如上所示,如果ID指定的Loader已經(jīng)存在,用這個(gè)。如果不是就創(chuàng)建一個(gè)。但是有時(shí)候你想丟棄老數(shù)據(jù)并重新開(kāi)始。
要丟棄你的老數(shù)據(jù),你用restartLoader()。例如,當(dāng)用得查詢隊(duì)列變更時(shí),這個(gè)SearchView.OnQueryTextListenerde實(shí)現(xiàn)會(huì)重啟Loader。Loader需要重啟,以便接受搜索過(guò)濾器做一次新的查詢:
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;
getSupportLoaderManager().restartLoader(0, null, this);
return true;
}
LoaderManager.LoaderCallbacks的使用
LoaderManager.LoaderCallbacks是一個(gè)回調(diào)接口,讓客戶端通過(guò)LoaderManager與之交互。
CursorLoader預(yù)計(jì)Loader在停止之后將保留其數(shù)據(jù)。允許應(yīng)用保持activity或fragment的onStop()和onStart()方法相關(guān)的數(shù)據(jù),當(dāng)用戶返回應(yīng)用程序時(shí)他們不必等待數(shù)據(jù)重載。你可以再LoaderManager.LoaderCallbacks方法合適使用這些方法創(chuàng)建一個(gè)新的Loader,并告訴應(yīng)用合適停止使用Loader的數(shù)據(jù)。
- onCreateLoader() - 實(shí)例化并Loader為給定的ID 返回一個(gè)新的。
- onLoadFinished() - 當(dāng)以前創(chuàng)建的加載程序完成加載時(shí)調(diào)用。
- onLoaderReset() - 當(dāng)以前創(chuàng)建的加載程序正在重置時(shí)調(diào)用,從而使其數(shù)據(jù)不可用。
這些方法在下面的章節(jié)中有更詳細(xì)的描述。
onCreateLoader
當(dāng)你試圖訪問(wèn)一個(gè)Loader(例如通過(guò)initLoader())時(shí),它會(huì)檢查該ID指定的Loader是否存在。如果沒(méi)有,則觸發(fā)LoaderManager.LoaderCallbacks的onCreateLoader()方法。這是你創(chuàng)建一個(gè)新的Loader的地方。通常將是CursorLoader(),但你能實(shí)現(xiàn)Loader的子類。
在這個(gè)例子中,onCreateLoader()回調(diào)方法創(chuàng)建一個(gè)CursorLoader。你必須用CursorLoader的構(gòu)造方法創(chuàng)建CursorLoader,這需要完整地信息集來(lái)執(zhí)行查詢ContentProvider。具體來(lái)說(shuō),它需要:
- uri - 藥檢所的內(nèi)容的URI
- projection - 要返回的列的列表。傳遞null將返回所有列,這是低效的。
- selection - 過(guò)濾器,聲明返回哪些行,格式化為SQL WHERE子句(不包括WHERE本身)。傳遞null將返回給定URI的所有行。
- selectionArgs - 你可以在選擇中包括 '?s' ,它們將按照它們出現(xiàn)在選擇中的順序由selectionArgs中的值替換。這些值將被綁定為字符串。
- sortOrder - 如何對(duì)行進(jìn)行排序,格式為SQL ORDER BY子句(不包括ORDER BY本身)。傳遞null將使用默認(rèn)的排序順序,可能是無(wú)序的。
例如:
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
當(dāng)之前創(chuàng)建的Loader已經(jīng)完成加載時(shí)此方法被調(diào)用。這個(gè)方法保證Loader提供最后的數(shù)據(jù)之前被調(diào)用。此時(shí),你應(yīng)該移除所有的老數(shù)據(jù)(因?yàn)樗芸毂会尫牛?,但是不?yīng)該你自己釋放數(shù)據(jù),因?yàn)樗腖oader擁有它并會(huì)照顧。
一旦應(yīng)用長(zhǎng)時(shí)間不使用loader將釋放數(shù)據(jù)。例如,如果數(shù)據(jù)是來(lái)自CursorLoader的一個(gè)cursor,你不應(yīng)該自己調(diào)用close()。如果cursor放在CursorAdapter中,你應(yīng)該用swapCursor()方法這樣老的cursor不關(guān)閉。例如:
// 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
當(dāng)已經(jīng)創(chuàng)建的Loader正在重置是調(diào)用這個(gè)方法,從而使數(shù)據(jù)不可用。這個(gè)回調(diào)讓你找出數(shù)據(jù)何時(shí)被釋放,這樣你就可以移除它的引用。
// 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);
}
舉例
作為一個(gè)例子,這里實(shí)現(xiàn)了一個(gè)完整地Fragment顯示一個(gè)LIstView,包含ContentProvider查詢聯(lián)系人內(nèi)容的查詢結(jié)果。它使用了CursorLoader管理provider的查詢。
對(duì)于訪問(wèn)用戶聯(lián)系人的應(yīng)用,如本例所示,manifest必須包含READ_CONTACTS權(quán)限。
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);
}
}
更多的例子
以下示例說(shuō)明如何使用裝載機(jī):
- LoaderCursor - 上面顯示的片段的完整版本。
- 檢索聯(lián)系人列表 - 使用CursorLoader從演示文稿提供程序檢索數(shù)據(jù)的演練。
- LoaderThrottle - 如何使用限制來(lái)減少內(nèi)容提供者在其數(shù)據(jù)更改時(shí)執(zhí)行的查詢數(shù)的示例。
- AsyncTaskLoader- 使用AsyncTaskLoader 從包管理器加載當(dāng)前安裝的應(yīng)用程序的示例。