Android 使用Loader示例

Android 3.0 sdk,引入了不少新api,好比Loader。和Fragment相似(編寫兼容android1.6的fragment),該api也可在Android 1.6以上版本執行。

如下介紹如何編寫Loader,實現對ListView的異步加載。效果如圖:html


示例中有一個後臺線程每隔3秒更新數據庫的長江記錄,將記錄改成「長江」或「Long River」。ListView無需監控數據庫變化,基於Loader,會自動更新。實際上這裏面是觀察者模式,無非是系統自帶了,只需調用便可,無需本身構造觀察者。java

這個示例也是完整的sqlite+content provider+cursor adapter+listview+loader組合示例。

編寫前的準備相似編寫兼容android1.6的fragment,須要導入jar包。
另外,2.3之前的Activity類沒有提供一些Loader的幫助方法,須要讓本身的Activity實現類繼承FragmentActivity:
public class ListViewActivity extends FragmentActivity
 

 本示例是在在視圖顯示中使用Theme基礎上實現的。
Activity類和RiverContentProvider類作了修改。
Activity類:android

public class ListViewActivity extends FragmentActivity {
 

private ListView riverListView;
 

private SimpleCursorAdapter adapter;
 
/** Called when the activity is first created. */
 

@Override 
public void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.main);
 

initLoader(); 
setRiverListViewAdapter(); 
}
 

private void initLoader() { 
getSupportLoaderManager().initLoader(0, null, 
new LoaderCallbacks<Cursor>() {
 

@Override 
public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
Log.d("list", "on create loader"); 
CursorLoader cursorLoader=new CursorLoader(ListViewActivity.this, 
RiverContentProvider.CONTENT_URI, new String[] { 
RiverContentProvider._ID, 
RiverContentProvider.NAME, 
RiverContentProvider.INTRODUCTION }, 
null, null, null); 
//cursorLoader.setUpdateThrottle(1000); return cursorLoader; 
}
 

@Override 
public void onLoadFinished(Loader<Cursor> loader, 
Cursor cursor) { 
Log.d("list", "on loader finished"); 
adapter.swapCursor(cursor); 
}
 

@Override 
public void onLoaderReset(Loader<Cursor> loader) { 
Log.d("list", "on loader reset"); 
adapter.swapCursor(null); 
} 
}); 
}
 

private void setRiverListViewAdapter() { 
riverListView = (ListView) this.findViewById(R.id.riverList);
 

Cursor cursor = managedQuery(RiverContentProvider.CONTENT_URI, null, 
null, null, null); 
adapter = new SimpleCursorAdapter(this, R.layout.row, cursor, 
new String[] { RiverContentProvider.NAME, 
RiverContentProvider.INTRODUCTION }, new int[] { 
R.id.riverName, R.id.riverIntroduction }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); 
riverListView.setAdapter(adapter); 
}
 

主要是增長了initLoader方法。這裏主要是實現了LoaderCallbacks接口。其中:

onCreateLoader,在建立activity時跟着onCreate會調用一次 
onLoadFinished,每次改變和Loader相關的數據庫記錄後會調用一次 
onLoaderReset,在關閉Activity時調用,釋放資源 

而後,在Content provider中,要調用相似觀察者模式中通知的方法,即,在update方法中通知觀察者記錄改變,在query方法中註冊觀察者,這樣通知來了可接收並處理。

update方法:sql

@Override 
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 
int returnValue = database.update("rivers", values, selection, selectionArgs); 
getContext().getContentResolver().notifyChange(uri, null); 
return returnValue; 
}
 

query方法:數據庫

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){ 
  Cursor cursor = database.query("rivers", projection, selection, selectionArgs, null, null, sortOrder);
  cursor.setNotificationUri(getContext().getContentResolver(), uri);
  return cursor;
}

 

-----------------------------------------------------------------------------------------------------------------------api

裝載器從android3.0開始引進。它使得在activity或fragment中異步加載數據變得簡單。裝載器具備以下特性:app

 

  • 它們對每一個Activity和Fragment都有效。框架

  • 他們提供了異步加載數據的能力。異步

  • 它們監視數據源的一將一動並在內容改變時傳送新的結果。ide

  • 當因爲配置改變而被從新建立後,它們自動重連到上一個加載器的遊標,因此沒必要從新查詢數據。

 

裝載器API概述

 


在使用裝載器時,會涉及不少類和接口們,咱們在下表中對它們總結一下:

 

Class/Interface

說明

LoaderManager

一個抽像類,關聯到一個ActivityFragment,管理一個或多個裝載器的實例。這幫助一個應用管理那些與ActivityFragment的生命週期相關的長時間運行的的操做。最多見的方式是與一個CursorLoader一塊兒使用,然而應用是能夠隨便寫它們本身的裝載器以加載其它類型的數據。

每一個activityfragment只有一個LoaderManager。可是一個LoaderManager能夠擁有多個裝載器。

LoaderManager.LoaderCallbacks

一個用於客戶端與LoaderManager交互的回調接口。例如,你使用回調方法onCreateLoader()來建立一個新的裝載器。

Loader(裝載器)

一個執行異步數據加載的抽象類。它是加載器的基類。你可使用典型的CursorLoader,可是你也能夠實現你本身的子類。一旦裝載器被激活,它們將監視它們的數據源而且在數據改變時發送新的結果。

AsyncTaskLoader

提供一個AsyncTask來執行異步加載工做的抽象類。

CursorLoader

AsyncTaskLoader的子類,它查詢ContentResolver而後返回一個Cursor。這個類爲查詢cursor以標準的方式實現了裝載器的協議,它的遊標查詢是經過AsyncTaskLoader在後臺線程中執行,從而不會阻塞界面。使用這個裝載器是從一個ContentProvider異步加載數據的最好方式。相比之下,經過fragmentactivityAPI來執行一個被管理的查詢就不行了。

 

 

上面所列的類和接口們是你在你的應用中要實現裝載器時的核心組件。你的每一個裝載器並不必定須要全部的組件,可是你老是須要引用LoaderManager來初始化一個裝載器。後面的章節將向你展現如何使用這些類和接口們。

 

一個使用裝載器的應用會典型的包含以下組件:

  • 一個ActivityFragment

  • 一個LoaderManager的實例.

  • 一個加載被ContentProvider所支持的數據的CursorLoader.或者,你能夠從LoaderAsyncTaskLoader實現你本身的裝載器來從其它源加載數據.

  • 一個LoaderManager.LoaderCallbacks的實現.這是你建立新的裝載器以及管理你的已有裝載器的引用的地方.

  • 一個顯示裝載器的數據的途徑,例如使用一個SimpleCursorAdapter

  • 一個數據源,好比當是用CursorLoader時,它將是一個ContentProvider

     

啓動一個裝載器

LoaderManager管理一個ActiviryFragment中的一個或多個裝載器.但每一個activityfragment只擁有一個LoaderManager

你一般要在activityonCreate()方法中或fragmentonActivityCreated()方法中初始化一個裝載器.你能夠以下建立:

 

  1. // 準備裝載器.能夠重連一個已經存在的也能夠啓動一個新的.  
  2. getLoaderManager().initLoader(0,nullthis);  



initLoader()方法有如下參數:

 

  • 一個惟一ID來標誌裝載器.在這個例子中,ID0

  • 可選的參數,用於裝載器初始化時(本例中是null)

  • 一個LoaderManager.LoaderCallbacks的實現.被LoaderManager調用以報告裝載器的事件,在這個例子中,類本實現了這個接口,因此傳的是它本身:this


initLoader()保證一個裝載器被初始化並激活.它具備兩種可能的結果:

  • 若是ID所指的裝載器已經存在,那麼這個裝載器將被重用.

  • 若是裝載器不存在,initLoader()就觸發LoaderManager.LoaderCallbacks的方法onCreateLoader().這是你實例化並返回一個新裝載器的地方.

 

在這兩種狀況中,傳入的LoaderManager.LoaderCallbacks的實現都與裝載器綁定在一塊兒.而且會在裝載器狀態變化時被調用.若是在調用這個方法時,調用者正處於啓動狀態,而且所請求的裝載器已存在併產生了數據,那麼系統會立刻調用onLoadFinished()(也就是說initLoader()還在執行時).因此你必須爲這種狀況的發生作好準備.


注意initLoader()返回所建立的裝載器,可是你不需保存一個對它的引用.LoaderManager自動管理裝載器的生命.LoaderManager會在須要時開始和中止裝載動做,而且維護裝載器的狀態和它所關聯的內容.這意味着,你不多與裝載器直接交互.你一般都是使用LoaderManager.LoaderCallbacks的方法們在某個事件發生時介入到數據加載的過程當中.

重啓裝載器

當你使用initLoader()時,若是指定ID的裝載器已經存在,則它使用這個裝載器.若是不存在呢,它將建立一個新的.可是有時你倒是想丟棄舊的而後開始新的數據.

要想丟棄舊數據,你應使用restartLoader().例如,下面這個SearchView.OnQueryTextListener的實如今用戶查詢發生改變時重啓了裝載器,裝載器因而需重啓從而能使用新的搜索過慮來進行一次新的查詢.

  1. <span style="font-family:KaiTi_GB2312;">public boolean onQueryTextChanged(String newText) {  
  2.     // 當動做欄的搜索字串發生改時被調用.  
  3.     // 更新搜索過慮,而後從新啓動裝載利用這個新過慮進行新的查詢.  
  4.     mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;  
  5.     getLoaderManager().restartLoader(0nullthis);  
  6.     return true;  
  7. }</span>  

使用LoaderManager的回調

LoaderManager.LoaderCallbacks是一個回調接口,它使得客戶端能夠與LoaderManager進行交互.

裝載器,通常指的是CursorLoader,咱們但願在它中止後依然保持數據.這使得應用能夠在activityfragment的 onStop() onStart() 之間保持數據,因此當用戶回到一個應用時,它們不需等待數據加載.你使用LoaderManager.LoaderCallbacks 的方法們,在須要時建立新的裝載器,而且告訴應用何時要中止使用裝載器的數據.

LoaderManager.LoaderCallbacks 包含如下方法們:

  • onCreateLoader() —跟據傳入的ID,初始化並返回一個新的裝載器.

  • onLoadFinished() —當一個裝載器完成了它的裝載過程後被調用.

  • onLoaderReset() —當一個裝載器被重置而什其數據無效時被調用.

onCreateLoader

當你試圖去操做一個裝載器時(好比,經過initLoader()),會檢查是否指定ID的裝載器已經存在.若是它不存在,將會觸發LoaderManager.LoaderCallbacks 的方法onCreateLoader().這是你建立一個新裝載器的地方.一般這個裝載器是一個CursorLoader,可是你也能夠實現你本身的裝載器.

在下面的例子中,回調方法onCreateLoader() 建立一個CursorLoader.你必須使用構造方法來創建CursorLoader ,構造方法須要向ContentProvider執行一次查詢的完整信息做爲參數,它尤爲須要:

  • uri —要獲取的內容的URI

  • projection —要返回的列組成的列被.傳入null 將會返回全部的列,但這是低效的.

  • selection —一個過濾器,代表哪些行將被返回.格式化成相似SQLWHERE 語句的樣子(除了沒有WHERE).傳入null 將返回全部的行.

  • selectionArgs —你能夠在selection 中包含一些'?',它將被本參數的值替換掉.這些值出現的順序與'?'selection中出現的順序一至.值將做爲字符串.

  • sortOrder —如何爲行們排序.格式化成相似於SQLORDER BY 語句的樣字(除了沒有ORDERBY).傳入null將使用默認順序,默認順序多是無順序.

例子:

  1.  // If non-null, this is the current filter the user has provided.  
  2. String mCurFilter;  
  3. ...  
  4. public Loader<Cursor> onCreateLoader(int id, Bundle args) {  
  5.     // 這裏是在須要建立新裝載器時被調用的.  
  6.     // 咱們只是簡單的擁有一個裝載器,因此咱們不須要關心ID.  
  7.     // First, pick the base URI to use depending on whether we are  
  8.     // currently filtering.  
  9.     Uri baseUri;  
  10.     if (mCurFilter != null) {  
  11.         baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,  
  12.                   Uri.encode(mCurFilter));  
  13.     } else {  
  14.         baseUri = Contacts.CONTENT_URI;  
  15.     }  
  16.     // Now create and return a CursorLoader that will take care of  
  17.     // creating a Cursor for the data being displayed.  
  18.     String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("  
  19.             + Contacts.HAS_PHONE_NUMBER + "=1) AND ("  
  20.             + Contacts.DISPLAY_NAME + " != '' ))";  
  21.     return new CursorLoader(getActivity(), baseUri,  
  22.             CONTACTS_SUMMARY_PROJECTION, select, null,  
  23.             Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");  
  24. }  
 
  

onLoadFinished

這個方法是在前面已建立的裝載器已經完成其加載過程後被調用.這個方法保證會在應用到裝載器上的數據被釋放以前被調用.在此方法中,你必須刪除全部對舊數據的使用(由於它將很快會被刪除),可是不要本身去釋放它們,由於它們的裝載器會作這些事情.

裝載器一旦瞭解到應用再也不使用數據時,將立刻釋放這些數據.例如,若是數據是一個從CursorLoader來的遊標,你不該調用遊標的close().若是遊標被放置在一個CursorAdapter中,你應使用swapCursor()方法,以使舊的遊標不被關閉.例如:

 

  1. //這個Adapter被用於顯示列表的數據.  
  2. SimpleCursorAdapter mAdapter;  
  3. ...  
  4.   
  5. public void onLoadFinished(Loader<Cursor> loader, Cursor data) {  
  6.     // Swap the new cursor in.  (The framework will take care of closing the  
  7.     // old cursor once we return.)  
  8.     mAdapter.swapCursor(data);  
  9. }  

 

onLoaderReset

當一個已建立的裝載器被重置從而使其數據無效時,此方法被調用.此回調使你能發現何時數據將被釋放因而你能夠釋放對它的引用.

下面這個實現調用參數爲nullswapCursor()

 

  1. // 這個Adapter被用於顯示列表的數據.  
  2. SimpleCursorAdapter mAdapter;  
  3. ...  
  4.   
  5. public void onLoaderReset(Loader<Cursor> loader) {  
  6.     //此處是用於上面的onLoadFinished()的遊標將被關閉時執行, 咱們需確保咱們再也不使用它.  
  7.     mAdapter.swapCursor(null);  
  8. }  

 

例子

做爲一個例子,這裏完整實現了一個Fragment顯示一個包含從聯繫人contentprovider 返回查詢數據的ListView的內容的功能.它使用一個CursorLoader來管理對provider的查詢.


爲了能從用戶的聯繫人中取得數據,本例的manifest必須包含READ_CONTACTS權限.

 

  1. public static class CursorLoaderListFragment extends ListFragment  
  2.         implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {  
  3.   
  4.     // 這是用於顯示列表數據的Adapter  
  5.     SimpleCursorAdapter mAdapter;  
  6.   
  7.     // 若是非null,這是當前的搜索過慮器  
  8.     String mCurFilter;  
  9.   
  10.     @Override public void onActivityCreated(Bundle savedInstanceState) {  
  11.         super.onActivityCreated(savedInstanceState);  
  12.   
  13.         // 若是列表中沒有數據,就給控件一些文字去顯示.在一個真正的應用  
  14.         // 中這應用資源中取得.  
  15.         setEmptyText("No phone numbers");  
  16.   
  17.         // 咱們在動做欄中有一個菜單項.  
  18.         setHasOptionsMenu(true);  
  19.   
  20.         // 建立一個空的adapter,咱們將用它顯示加載後的數據  
  21.         mAdapter = new SimpleCursorAdapter(getActivity(),  
  22.                 android.R.layout.simple_list_item_2, null,  
  23.                 new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },  
  24.                 new int[] { android.R.id.text1, android.R.id.text2 }, 0);  
  25.         setListAdapter(mAdapter);  
  26.   
  27.         // 準備loader.多是重連到一個已存在的或開始一個新的  
  28.         getLoaderManager().initLoader(0nullthis);  
  29.     }  
  30.   
  31.     @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {  
  32.         // 放置一個動做欄項用於搜索.  
  33.         MenuItem item = menu.add("Search");  
  34.         item.setIcon(android.R.drawable.ic_menu_search);  
  35.         item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);  
  36.         SearchView sv = new SearchView(getActivity());  
  37.         sv.setOnQueryTextListener(this);  
  38.         item.setActionView(sv);  
  39.     }  
  40.   
  41.     public boolean onQueryTextChange(String newText) {  
  42.         // 在動做欄上的搜索字串改變時被調用.更新  
  43.         //搜索過濾器,並重啓loader來執行一個新的查詢  
  44.         mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;  
  45.         getLoaderManager().restartLoader(0nullthis);  
  46.         return true;  
  47.     }  
  48.   
  49.     @Override public boolean onQueryTextSubmit(String query) {  
  50.         // 咱們不關心這個方法  
  51.         return true;  
  52.     }  
  53.   
  54.     @Override public void onListItemClick(ListView l, View v, int position, long id) {  
  55.         // 寫入你想寫的代碼  
  56.         Log.i("FragmentComplexList""Item clicked: " + id);  
  57.     }  
  58.   
  59.     // 這是咱們想獲取的聯繫人中一行的數據.  
  60.     static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {  
  61.         Contacts._ID,  
  62.         Contacts.DISPLAY_NAME,  
  63.         Contacts.CONTACT_STATUS,  
  64.         Contacts.CONTACT_PRESENCE,  
  65.         Contacts.PHOTO_ID,  
  66.         Contacts.LOOKUP_KEY,  
  67.     };  
  68.     public Loader<Cursor> onCreateLoader(int id, Bundle args) {  
  69.         // 當一個新的loader需被建立時調用.本例僅有一個Loader,  
  70.         //因此咱們不需關心ID.首先設置base URI,URI指向的是聯繫人  
  71.         Uri baseUri;  
  72.         if (mCurFilter != null) {  
  73.             baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,  
  74.                     Uri.encode(mCurFilter));  
  75.         } else {  
  76.             baseUri = Contacts.CONTENT_URI;  
  77.         }  
  78.   
  79.         // 如今建立並返回一個CursorLoader,它將負責建立一個  
  80.         // Cursor用於顯示數據  
  81.         String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("  
  82.                 + Contacts.HAS_PHONE_NUMBER + "=1) AND ("  
  83.                 + Contacts.DISPLAY_NAME + " != '' ))";  
  84.         return new CursorLoader(getActivity(), baseUri,  
  85.                 CONTACTS_SUMMARY_PROJECTION, select, null,  
  86.                 Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");  
  87.     }  
  88.   
  89.     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {  
  90.         // 將新的cursor換進來.(框架將在咱們返回時關心一下舊cursor的關閉)  
  91.         mAdapter.swapCursor(data);  
  92.     }  
  93.   
  94.     public void onLoaderReset(Loader<Cursor> loader) {  
  95.         //在最後一個Cursor準備進入上面的onLoadFinished()以前.  
  96.         // Cursor要被關閉了,咱們須要確保再也不使用它.  
  97.         mAdapter.swapCursor(null);  
  98.     }  
  99. }   


相關文章
相關標籤/搜索