Loader用法
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
1. Loader簡(jiǎn)介
從3.0開始,Android引入了Loader API。它可以幫我我們從content provider或其他數(shù)據(jù)源獲取數(shù)據(jù)并顯示在UI界面上。
如果沒有Loader API:
- 我們?cè)赨I線程中獲取數(shù)據(jù),如果耗時(shí)則界面會(huì)卡頓。
- 我們?cè)诜荱I線程中獲取數(shù)據(jù),比如通過AsycTask,那么我們需要處理activity或fragment生命周期所觸發(fā)的event,比如onDestory()和configuration changes。非UI線程和UI線程我們都需要做處理。
Loader可以幫助我們解決上面的問題:
- Loader運(yùn)行在非UI線程。
- Loader提供了回調(diào)方法,用以應(yīng)對(duì)event的發(fā)生,簡(jiǎn)化了線程的管理。
- Loader在configuration changes時(shí)會(huì)對(duì)數(shù)據(jù)進(jìn)行保存和緩存,防止重復(fù)的獲取。
- Loader實(shí)現(xiàn)了對(duì)數(shù)據(jù)源的監(jiān)控(觀察者)。比如CursorLoader會(huì)注冊(cè)一個(gè)ContentObserver,在數(shù)據(jù)發(fā)生改變的時(shí)候會(huì)自動(dòng)觸發(fā)重加載。
2. Loader API概述
| LoaderManager | 一個(gè)Activity或Fragment可能有多個(gè)Loader實(shí)例。但只能有一個(gè)LoaderManager,它能管理多個(gè)Loader。通過getLoaderManager()獲取LoaderMananger實(shí)例。 從loader中獲取數(shù)據(jù),可以調(diào)用initLoader() 或 restartLoader()。系統(tǒng)會(huì)自動(dòng)判斷包含相同ID的loader是否已經(jīng)存在,從而重新創(chuàng)建或者復(fù)用已有的loader。 |
| LoaderManager.LoaderCallbacks | 這個(gè)接口里的回調(diào)方法會(huì)在loader events觸發(fā)時(shí)調(diào)用。定義了三個(gè)回調(diào)方法:
通常這個(gè)接口我們需要在activitiy/fragment中實(shí)現(xiàn),并在調(diào)用initLoader() 或 restartLoader()之前注冊(cè)。 |
| Loader | Loders負(fù)責(zé)加載數(shù)據(jù)。這是個(gè)抽象類,同時(shí)也是所有類型loader的基類。我們可以自己繼承Loader 或者 直接使用系統(tǒng)的Loader子類:
|
下面會(huì)討論如何使用這些類。
3. Using Loaders in an Application
本節(jié)會(huì)討論如何使用loaders。步驟如下:
- 在Activity或Fragment中。
- 聲明一個(gè)LoaderManager實(shí)例。
- 一個(gè)CursorLoader用來從ContentProvider中獲取數(shù)據(jù)。如果我們需要從其他的數(shù)據(jù)源獲取數(shù)據(jù),我們可以自己實(shí)現(xiàn)Loader或AsyncTaskLoader的子類。
- 實(shí)現(xiàn)LoaderManager.LoaderCallbacks。我們?cè)诨卣{(diào)中創(chuàng)建并管理loaders。
- 選擇一個(gè)展示loader數(shù)據(jù)的方式,比如SimpleCursorAdapter。
- 選擇數(shù)據(jù)源,比如ContentProvider,我們使用CursorLoader來獲取數(shù)據(jù)。
3.1 Starting a Loader
LoaderManager在Activity或Fragment中管理一個(gè)或多個(gè)Loader的實(shí)例。一個(gè)Activity或Fragment中只能有一個(gè)LoaderMananger。
通常我們?cè)趏nCreate()/onActivityCreated()中初始化一個(gè)Loader。
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this);initLoder()方法有兩個(gè)參數(shù):
- 唯一的ID用來標(biāo)記標(biāo)記loader。
- 可選的參數(shù),在loader構(gòu)造時(shí)會(huì)使用。沒有傳null。
- LoaderManager.LoaderCallbacks的實(shí)現(xiàn),LoaderManager會(huì)負(fù)責(zé)進(jìn)行回調(diào)。
調(diào)用initLoader()之后,一個(gè)loader被初始化并且可用。存在兩種可能的返回值:
- 如果指定ID對(duì)應(yīng)的loader已經(jīng)存在,則返回這個(gè)loader。
- 如果指定ID對(duì)應(yīng)的loader不存在,initLoader()方法會(huì)觸發(fā)callback中的onCreateLoader()回調(diào)。前面提到,我們會(huì)在這個(gè)回調(diào)中自己創(chuàng)建loader的方法。更詳細(xì)的介紹可以參考o(jì)nCreateLoader章節(jié)。
同時(shí),返回的loader會(huì)跟LoaderMananger.LoaderCallbacks綁定,在這個(gè)loader有狀態(tài)改變的時(shí)候回調(diào)都會(huì)被觸發(fā)。如果在請(qǐng)求創(chuàng)建過程中,請(qǐng)求的loader已經(jīng)存在同時(shí)產(chǎn)生了數(shù)據(jù),回調(diào)onLoadFinish()會(huì)立刻被創(chuàng)建(還在initLoader()的過程中),所以我們需要考慮這種情況的判斷。更詳細(xì)的介紹可以參考o(jì)nLoaderFinished章節(jié)。
需要注意的是,雖然initLoader()方法會(huì)返回一個(gè)Loader實(shí)例,但我們不用去引用它。LoaderManager會(huì)自動(dòng)地在loader的生命周期中對(duì)它進(jìn)行管理。LoaderMananger會(huì)自動(dòng)在適當(dāng)時(shí)機(jī)開始或停止loader,并保存loader的狀態(tài)和它關(guān)聯(lián)的數(shù)據(jù)。這意味著,我們不用直接對(duì)Loader進(jìn)行操作(除非我們需要額外地管理loader的行為,可以參考例子LoaderThrottle)。我們只需要在LoaderManager.LoaderCallbacks的回調(diào)方法中進(jìn)行處理。更詳細(xì)的介紹可以參考Using the LoadManager Callbacks章節(jié)。
3.2 Restarting a Loader
正如前面介紹的,initLoader()在我們制定ID對(duì)應(yīng)的Loader不存在時(shí)才會(huì)創(chuàng)建一個(gè)新的,如果存在則直接復(fù)用。但有的時(shí)候,我們需要丟棄舊的數(shù)據(jù),使用新的。這時(shí)候我們可以使用restartLoader()方法。
比如,我們?cè)趯?shí)現(xiàn)SearchView.OnQueryTextListener的時(shí)候,當(dāng)我們需要查詢數(shù)據(jù)的條件發(fā)生改變時(shí),我們需要改變search filter之后restart loader。
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; }3.3 Using the LoaderMananger Callbacks
我們可以通過LoaderMananger.LoaderCallbacks的回調(diào)方法與LoaderManager進(jìn)行交互。這可以使我們知道loader什么時(shí)候被創(chuàng)建、什么時(shí)候被停止,進(jìn)而對(duì)我們的UI進(jìn)行更新。
LoaderMananger.LoaderCallbacks包含了3個(gè)回調(diào)方法:
- onCreateLoader() - 初始化并返回指定ID對(duì)應(yīng)的Loader。
- onLoadFinished() - 當(dāng)loader完成數(shù)據(jù)加載會(huì)被調(diào)用。
- onLoaderReset() - 當(dāng)loader被reset時(shí)調(diào)用,這會(huì)導(dǎo)致數(shù)據(jù)無法使用。
3.3.1 onCreateLoader
當(dāng)我們嘗試訪問loader,比如initLoader(),它會(huì)檢查ID對(duì)應(yīng)的loader是否存在。如果不存在,則觸發(fā)LoaderManager.LoaderCallbacks的onCreateLoader()。我們?cè)谶@個(gè)回調(diào)里創(chuàng)建loader。通常是CursorLoader,也可以自己實(shí)現(xiàn)Loader的子類。
舉個(gè)例子,如果我們創(chuàng)建CursorLoader。我們需要構(gòu)造函數(shù)創(chuàng)建CursorLoader,如果用它查詢ContentProvider,我們需要:
- uri - 查詢數(shù)據(jù)的URI。
- projection - 指定會(huì)返回哪些columns。傳遞null會(huì)返回所有的columns,不過這樣效率很低。
- selection - 指定會(huì)返回哪些rows,格式是WHERE SQL語(yǔ)句。傳遞null會(huì)返回所有的rows。
- selectionArgs - 我們可以在selection中使用通配符?,它會(huì)被selectionArgs中的值代替。格式是Strings。
- sortOrder - 設(shè)置查詢順序,格式是ORDER BY SQL語(yǔ)句。傳遞null會(huì)按照默認(rèn)順序,也許是無序。
比如
// 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"); }3.3.2 onLoadFinished
// 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); }3.3.3 onLoaderReset
這個(gè)回調(diào)可以幫助我們知道什么時(shí)候數(shù)據(jù)被釋放,我們可以釋放對(duì)數(shù)據(jù)的引用。
比如在下面的例子,我們調(diào)用swapCursor()并傳null:
// 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); }4. Example
下面是在Fragment中使用listview展示從contacts content provider獲取到的數(shù)據(jù)。使用CursorLoader管理數(shù)據(jù)。
因?yàn)檫@個(gè)例子需要訪問聯(lián)系人信息,所以我們需要在manifest中添加READ_CONTACTS權(quán)限。
public static class CursorLoaderListFragment extends ListFragmentimplements 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);} }4.1 More Examples
其他使用loader的場(chǎng)景:
- LoaderCursor - 如上面的例子。
- Retrieving a List of Contacts
- LoaderThrottle
- AsyncTaskLoader - 通過AsyncTaskLoader從package manager中獲取已安裝的app信息。
參考資料
筆記: Loader 加載器
轉(zhuǎn)載于:https://my.oschina.net/u/3026396/blog/824550
總結(jié)
- 上一篇: opencv3——ANN算法的使用
- 下一篇: spring配置文件中非bean标签的原