Android 使用Loader示例

Android 3.0 sdk,引入了很多新api,比如Loader。和Fragment类似(编写兼容android1.6的fragment),该api也可在Android 1.6以上版本执行。

以下介绍如何编写Loader,实现对ListView的异步加载。效果如图:


示例中有一个后台线程每隔3秒更新数据库的长江记录,将记录改为“长江”或“Long River”。ListView无需监控数据库变化,基于Loader,会自动更新。实际上这里面是观察者模式,无非是系统自带了,只需调用即可,无需自己构造观察者。

这个示例也是完整的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类:

复制代码
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方法:

复制代码
@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;
}

 

-----------------------------------------------------------------------------------------------------------------------

装载器从android3.0开始引进。它使得在activityfragment中异步加载数据变得简单。装载器具有如下特性:

 

  • 它们对每个ActivityFragment都有效。

  • 他们提供了异步加载数据的能力。

  • 它们监视数据源的一将一动并在内容改变时传送新的结果。

  • 当由于配置改变而被重新创建后,它们自动重连到上一个加载器的游标,所以不必重新查询数据。

 

装载器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. }  
posted @ 2013-01-09 11:30 Hecker385 阅读(...) 评论(...) 编辑 收藏