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. }   


相关文章
相关标签/搜索