以下所述内容参考自:http://developer.android.com/guide/components/loaders.html。
为了在Activity或Fragment中更方便的异步加载数据,从Android 3.0版本(API Level 11)开始引入Loader,Loader的特点有:每个Activity和Fragment都可以使用Loader;它提供的是异步加载数据的方式;它监听数据源的改变并在数据内容改变时分发新的数据结果;在某个配置改变后Loader被重新创建时,会自动重新连接到最近使用过的Loader的游标处,因此就不需要重新查询加载数据。
在应用程序中使用Loader时,有一些类和接口通常会使用到,主要有以下:
LoaderManager:是与某个特定的Activity或Fragment关联的抽象类,可以管理一个或多个Loader实例。它的作用是在Activity或Fragment的生命周期过程中辅助应用程序完成一些需要长时间运行的操作。它最通常的是与一个CursorLoader实例结合使用,但也可以使用自定义的Loader类实例来加载其他相应类型的数据。
LoaderManager.LoaderCallbacks:与LoaderManager交互的一个回调接口,例如可以在其回调函数onCreateLoader()创建一个新的Loader实例。
Loader:执行异步加载数据的抽象类,通常使用的是它的子类CursorLoader,但也可以使用自定义的Loader子类。当Loader处于活动状态时,应该监听相应数据源的改变并在数据内容改变时分发新的数据结果。
AsyncTaskLoader:使用一个AsyncTask来执行操作的抽象Loader类。
CursorLoader:是AsyncTaskLoader的一个子类,因此使用了一个后台线程来异步加载数据。主要用于加载ContentProvider中的数据,并与查询返回的Cursor对象相关联。相比在Activity或Fragment中直接使用其他API函数来加载数据,它由于使用了后台线程来执行这些操作,不会阻塞UI线程,因此是加载ContentProvider数据的最佳方式。
应用程序中使用Loader加载数据通常包括以下内容:一个Activity或Fragment;一个LoaderManager;一个CursorLoader用于加载ContentProvider中的数据,当然也可以通过自己创建的一个Loader类的子类或AsyncTaskLoader类的子类来加载其他类型的数据;实现LoaderManager.LoaderCallbacks接口,在这个接口中创建并管理Loader对象;显示出Loader加载的数据内容,例如可以使用SimpleCursorAdapter来显示;要加载的数据源,例如在使用CursorLoader时的数据源是一个ContentProvider。
通常在Activity的onCreate()函数或Fragment的onActivityCreated()函数中初始化Loader对象实例,使用语句:getLoaderManager().initLoader(0, null, this); initLoader()函数的第一个参数是一个标识Loader对象的ID,这个语句中用的ID是0;第二个参数是一个可选参数,第三个参数是一个实现了LoaderManager.LoaderCallbacks接口的对象,由于在相应Activity或Fragment中实现了这个接口,所以这个语句用的参数是this。initLoader()执行后,Loader对象就初始化完成了并处于活动状态,且有两种可能的结果:如果第一个参数中使用的ID号正与其他已创建的Loader对象关联,那么就直接使用那个已创建的Loader对象;如果ID号没有被其他Loader使用,就触发LoaderManager.LoaderCallbacks接口中的onCreateLoader()函数,在这个函数中需要实现创建并返回新的Loader对象的代码。在这两种结果中,第一种不会调用onCreateLoader()函数而第二种会调用,但紧接着当数据加载完成后,无论两者中的哪种情况,都会调用LoaderManager.LoaderCallbacks接口中的onLoadFinished()函数。onCreateLoader()函数中返回的Loader对象不需要直接去引用,LoaderManager会自动管理这个Loader对象并维护它相关联的数据。
如果需要丢弃Loader中加载的数据并重新加载,例如一个号码快速查询程序,输入号码内容改变时,查询出的结果也应该立即更新,就需要重新加载数据,在这种情况下就可以使用语句getLoaderManager().restartLoader(0, null, this); 来重新开始加载新的数据,执行此语句后,LoaderManager.LoaderCallbacks接口中的onCreateLoader()和onLoadFinished()函数也会依次被调用。
在上面提到的onLoadFinished()函数,其功能主要是将Loader加载的Cursor对象数据保存下来,例如在用一个SimpleCursorAdapter对象保存Cursor数据时使用以下代码:
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);
}
swapCursor()函数的功能即将data数据绑定到mAdapter中,执行此函数后,如果之前绑定过旧的data数据,则旧的data数据会自动回收掉。LoaderManager.LoaderCallbacks接口中还有一个回调函数onLoaderReset(),当Activity或Fragment被Destroyed后会调用此函数(如果只是被Stopped则不会调用此函数),因此可以在此函数中回收释放Loader加载的Cursor对象,如下代码所示:
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);
}
这里在swapCursor()函数中传入参数null,即将mAdapter绑定到null,之前使用过的数据即会被系统自动回收释放了。
下面通过一个例子演示Loader的使用,程序的功能是显示出手机上的联系人姓名及号码,当在搜索框中输入搜索的联系人姓名或号码的部分内容后,显示出与内容相匹配的联系人,下面列出程序主要文件内容,包括manifest文件和源代码文件(没有使用布局文件),共2个文件:
\AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.study.loader"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="15" />
<!-- 要读取联系人数据,需要加入以下权限声明 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\src\com\study\loader\MainActivity.java:
package com.study.loader;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.ListFragment;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.SimpleCursorAdapter;
import android.app.LoaderManager;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
CursorLoaderListFragment list = new CursorLoaderListFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
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[] { CommonDataKinds.Phone.DISPLAY_NAME, CommonDataKinds.Phone.NUMBER },
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;
}
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[] {
CommonDataKinds.Phone._ID,
CommonDataKinds.Phone.DISPLAY_NAME,
CommonDataKinds.Phone.NUMBER,
};
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(CommonDataKinds.Phone.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = CommonDataKinds.Phone.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + CommonDataKinds.Phone.DISPLAY_NAME + " NOTNULL) AND ("
+ CommonDataKinds.Phone.HAS_PHONE_NUMBER + "=1) AND ("
+ CommonDataKinds.Phone.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
CommonDataKinds.Phone.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);
}
}
}