装载器从android3.0开始引进。它使得在activity或fragment中异步加载数据变得简单。
当成批显示数据的时候,为了使用户体验更好,需要进行异步装载。也就是说,让未显示数据的ListView等UI组件或控件先显示,避免出现白屏的尴尬现象,同时在后台下载数据,等下载完成后再更新ListView组件。这样尽管用户不会立刻看到数据,但是也不至于网络速度缓慢或服务器响应不及时而造成假死现象。
装载器具有如下特性:
-
它们对每个Activity和Fragment都有效;
-
他们提供了异步加载数据的能力;
-
它拥有一个数据改变通知机制,当数据源做出改变时会及时通知。 也就是可以监听数据源,一旦数据源发生变化,Loader会感知这些变化;
-
当Cursor 发生变化时,会自动加载数据,因此并不需要再重新进行数据查询。
android设计Loader的初衷是想让大家像CursorLoader的做法一样,通过loader去维护数据,每次启动loader时先检查有没有旧的数据并把旧的数据先deliver给用户,然后再考虑要不要重新加载新的数据。
(二)、装载器API概述:
在使用装载器时,会涉及很多类和接口们,在下表中对它们总结一下:
Class/Interface |
说明 |
LoaderManager |
一个抽像类,关联到一个Activity或Fragment,管理一个或多个装载器的实例。这帮助一个应用管理那些与Activity或Fragment的生命周期相关的长时间运行的的操作。最常见的方式是与一个CursorLoader一起使用,然而应用是可以随便写它们自己的装载器以加载其它类型的数据。 每个activity或fragment只有一个LoaderManager。但是一个LoaderManager可以拥有多个装载器。 |
LoaderManager.LoaderCallbacks |
一个用于客户端与LoaderManager交互的回调接口。例如,你使用回调方法onCreateLoader()来创建一个新的装载器。 |
Loader(装载器) |
一个执行异步数据加载的抽象类。它是加载器的基类。你可以使用典型的CursorLoader,但是你也可以实现你自己的子类。一旦装载器被激活,它们将监视它们的数据源并且在数据改变时发送新的结果。 |
AsyncTaskLoader |
提供一个AsyncTask来执行异步加载工作的抽象类。 |
CursorLoader |
AsyncTaskLoader的子类,它查询ContentResolver然后返回一个Cursor。这个类为查询cursor以标准的方式实现了装载器的协议,它的游标查询是通过AsyncTaskLoader在后台线程中执行,从而不会阻塞界面。使用这个装载器是从一个ContentProvider异步加载数据的最好方式。相比之下,通过fragment或activity的API来执行一个被管理的查询就不行了。 |
(三)、类目录结构:
1、API11中开始加入Loader:
java.lang.Object
↳ android.content.Loader<D>
↳ android.content.AsyncTaskLoader<D>
子类:
java.lang.Object
↳ android.content.Loader<D>
↳ android.content.AsyncTaskLoader<D>
↳ android.content.CursorLoader
2、为了兼容1.6以下版本:
java.lang.Object
↳ android.support.v4.content.Loader<D>
↳ android.support.v4.content.AsyncTaskLoader<D>
子类:
java.lang.Object
↳ android.support.v4.content.Loader<D>
↳ android.support.v4.content.AsyncTaskLoader<D>
↳ android.support.v4.content.CursorLoader
二、AsyncTaskLoader示例:
(一)、AsyncTaskLoader 实现数据加载的步骤:
1、窗体Activity要实现LoaderManager.LoaderCallbacks<Cursor>接口。至于继承于Activity还是FragmentActivity要看是否需要支持3.0以下版本。如果需要兼容则继承于FragmentActivity。
2、创建LoaderManager对象:通过getLoaderManager()或getSupportLoaderManager()方法来实现。如果是继承于FragmentActivity类,则使用getSupportLoaderManager()方法来创建LoaderManger对象,否则使用前者创建即可;
3、初始化LoaderManager对象:调用initLoader()方法来初始化;
- initLoader()方法有以下参数:
-
- 一个唯一ID来标志装载器
- 可选的参数,用于装载器初始化时
- 一个LoaderManager.LoaderCallbacks的实现。被LoaderManager调用以报告装载器的事件。一般窗体都实现了这个接口,所以传的是它自己:this;
- initLoader()保证一个装载器被初始化并激活.它具有两种可能的结果:
-
- 如果ID所指的装载器已经存在,那么这个装载器将被重用;
- 如果装载器不存在,initLoader()就触发LoaderManager.LoaderCallbacks中的回调方法onCreateLoader()。这是实例化并返回一个新Loader的地方。
5、自定义Loader,作为onCreateLoader()的返回值(也就是说onCreateLoader()方法必须返回自定义Loader的实例);
- 自定义Loader要继承于AsyncTaskLoader<Cursor>;
- 必须要有构造方法;
- 必须重写onStartLoading()、loadInBackground() 、deliverResult()。而且要在onStartLoading中调用forceLoad()才能依次调用下一个即将执行的方法。
-
- 在loadInBackground()方法中执行数据库查询,返回Cursor;
- 在deliverResult()方法中执行跟适配器交换数据的操作。adapter.swapCursor(data)。
- Flags used to determine the behavior of the adapter; may be any combination of
FLAG_AUTO_REQUERY
andFLAG_REGISTER_CONTENT_OBSERVER。
- FLAG_AUTO_REQUERY(常量值:1 )从 API11 开始已经废弃。因为他会在应用程序的 UI 线程中执行游标查询操作, 导致响应缓慢甚至应用程序无响应(ANR)的错误。作为替代方案,请使用 LoaderManager 和 AsyncTaskLoader、CursorLoader。
- 如果设置FLAG_REGISTER_CONTENT_OBSERVER(常量值:2),适配器会在Cursor上注册一个内容观测器,当通知到达时会调用 onContentChanged() 方法。
当一个已创建的装载器被重置从而使其数据无效时,此方法被调用.此回调使你能发现什么时候数据将被释放。你可以释放对它的引用。
04-01 04:00:25.477: MainActivity: ==onCreate 04-01 04:00:25.701: LoaderCallbacks: ==onCreateLoader 04-01 04:00:25.705: AsyncTaskLoader: ==onStartLoading 04-01 04:00:25.709: AsyncTaskLoader: ==loadInBackground 04-01 04:00:25.721: AsyncTaskLoader: ==deliverResult 04-01 04:00:25.721: LoaderCallbacks: ==onLoadFinished 04-01 04:00:25.749: MainActivity: ==onStart 04-01 04:00:25.749: MainActivity: ==onResume 04-01 04:00:25.973: MainActivity: ==onCreateOptionsMenu
04-01 04:01:06.693: MainActivity: ==onPause 04-01 04:01:08.413: MainActivity: ==onStop 04-01 04:01:15.721: AsyncTaskLoader: ==onStartLoading 04-01 04:01:15.721: AsyncTaskLoader: ==loadInBackground 04-01 04:01:15.721: AsyncTaskLoader: ==deliverResult 04-01 04:01:15.721: LoaderCallbacks: ==onLoadFinished 04-01 04:01:15.757: MainActivity: ==onStart 04-01 04:01:15.757: MainActivity: ==onResume
(三)、示例代码:
/** * 数据源:ContentProvider----》Sms 读取短信---》权限 * * ListVIew---》变化---》sv搜索内容的变化 * */ public class MainActivity extends Activity implements LoaderCallbacks<Cursor> { private SearchView sv; private ListView lv; private SimpleCursorAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sv = (SearchView) findViewById(R.id.sv); // 添加文本内容改变监听 sv.setOnQueryTextListener(new OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { return false; } // 文本内容改变过程中调用---》参数:最新的文本内容 @Override public boolean onQueryTextChange(String newText) { Bundle bundle = new Bundle(); bundle.putString("newText", newText); // 重启Loader--->参数2:传值 getLoaderManager().restartLoader(1, bundle, MainActivity.this); return false; } }); lv = (ListView) findViewById(R.id.lv); adapter = new SimpleCursorAdapter(MainActivity.this, android.R.layout.simple_list_item_2, null, new String[] { "address", "body" }, new int[] { android.R.id.text1, android.R.id.text2 }); lv.setAdapter(adapter); // 使用Loader数据加载---》实例化loader // 参数1:实例化的Loader的id // 参数2:传递参数onCreateLoader()方法的第二个参数 // 参数3:回调接口 getLoaderManager().initLoader(1, null, this); } // 创建Loader @Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { CursorLoader loader; if (bundle != null) {// 有查询条件---》模糊匹配短信内容 // 条件值取出 String str = bundle.getString("newText"); //参数1: 参数2:Uri 参数3:要查询的列 参数4:查找条件 参数5:条件中的占位符的值 参数6:排序 loader = new CursorLoader(MainActivity.this, Uri.parse("content://sms"), null, "body like '%" + str + "%'", null, null); } else { loader = new CursorLoader(MainActivity.this, Uri.parse("content://sms"), null, null, null, null); } return loader; } // 数据加载完成---》参数2:加载到的新数据---》替换新数据 @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // 把Adapter中的数据替换为最新数据 adapter.swapCursor(data); } // Loader被重置的时候调用该方法---》释放最后加载的cursor @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); } }
三、CursorLoader实例:
(一)、CursorLoader 实现数据加载的步骤:
1、窗体Activity要实现LoaderManager.LoaderCallbacks<Cursor>接口。至于继承于Activity还是FragmentActivity要看是否需要支持3.0以下版本。如果需要兼容则继承于FragmentActivity。
2、创建LoaderManager对象:通过getLoaderManager()或getSupportLoaderManager()方法来实现。如果是继承于FragmentActivity类,则使用getSupportLoaderManager()方法来创建LoaderManger对象,否则使用前者创建即可;
3、初始化LoaderManager对象:调用initLoader()方法来初始化;
- initLoader()方法有以下参数:
-
- 一个唯一ID来标志装载器
- 可选的参数,用于装载器初始化时
- 一个LoaderManager.LoaderCallbacks的实现。被LoaderManager调用以报告装载器的事件。一般窗体都实现了这个接口,所以传的是它自己:this;
- initLoader()保证一个装载器被初始化并激活.它具有两种可能的结果:
-
- 如果ID所指的装载器已经存在,那么这个装载器将被重用;
- 如果装载器不存在,initLoader()就触发LoaderManager.LoaderCallbacks中的回调方法onCreateLoader()。这是实例化并返回一个新Loader的地方。
【备注:】new CursorLoader()的参数:
1、 uri —要获取的内容的URI;
2、projection —要返回的列组成的数组。传入null 将会返回所有的列,但这样会导致低效;
3、selection —表明哪些行将被返回,相当于SQL语句中的WHERE条件 (不包括WHERE关键词)。传入null 将返回所有的行;
4、selectionArgs —Where语句中的'?’组成的数组。
5、sortOrder —如何排序,相当于SQL语句中的 ORDER BY 语句(不包括ORDER BY关键词)。传入null将使用默认顺序。
(二)、示例代码:
publicclass MainActivity extends Activity implements LoaderCallbacks<Cursor> { private ListView listView_main; private Uri uri = Uri .parse("content://com.steven.mywordsprovider/tb_words"); private SimpleCursorAdapter adapter = null; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("MainActivity", "==onCreate"); listView_main = (ListView) findViewById(R.id.listView_main); adapter = new SimpleCursorAdapter(this, R.layout.item_listview, null, new String[] { "words", "detail" }, newint[] { R.id.text_item_listview_id, R.id.text_item_listview_title }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); listView_main.setAdapter(adapter); LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(2, null, this); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Log.i("Callbacks", "==onCreateLoader"); returnnew CursorLoader(this, uri, null, null, null, null); } @Override publicvoid onLoadFinished(Loader<Cursor> loader, Cursor data) { Log.i("Callbacks", "==onLoadFinished"); adapter.swapCursor(data); } @Override publicvoid onLoaderReset(Loader<Cursor> loader) { Log.i("Callbacks", "==onLoaderReset"); adapter.swapCursor(null); } @Override publicboolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); Log.i("MainActivity", "==onCreateOptionsMenu"); returntrue; } }
四、CursorLoader结合ActionBar上的SearchView实现过滤搜索:
(一)、主窗体布局核心代码:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ListView
android:id="@+id/listView_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
<TextView
android:id="@+id/text_emptyinfo"
android:gravity="center"
android:textSize="24sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="没有该词汇!"/>
</LinearLayout>
(二)、Menu菜单的xml:
<menuxmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/searchView_actionBar"
android:showAsAction="always"
android:actionViewClass="android.widget.SearchView"
android:title="搜索"/>
</menu>
(三)、主窗体Java核心代码:
publicclass MainActivity extends Activity implements LoaderCallbacks<Cursor> {
private ListView listView_main;
private TextView text_emptyinfo;
private SimpleCursorAdapter adapter = null;
private String uri_path;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("MainActivity", "==onCreate");
listView_main = (ListView) findViewById(R.id.listView_main);
text_emptyinfo = (TextView) findViewById(R.id.text_emptyinfo);
adapter = new SimpleCursorAdapter(this, R.layout.item_listview, null,
new String[] { "words", "detail" }, newint[] {
R.id.text_item_listview_id,
R.id.text_item_listview_title },
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView_main.setAdapter(adapter);
listView_main.setEmptyView(text_emptyinfo);
LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(2, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.i("Callbacks", "==onCreateLoader");
Uri baseUri = Uri
.parse("content://com.steven.mywordsprovider/tb_words");
if (uri_path != null) {
baseUri = Uri.withAppendedPath(baseUri, uri_path);
}
returnnew CursorLoader(this, baseUri, null, null, null, null);
}
@Override
publicvoid onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.i("Callbacks", "==onLoadFinished");
adapter.swapCursor(data);
}
@Override
publicvoid onLoaderReset(Loader<Cursor> loader) {
Log.i("Callbacks", "==onLoaderReset");
adapter.swapCursor(null);
}
@Override
publicboolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
SearchView searchView_actionBar = (SearchView) menu.findItem(
R.id.searchView_actionBar).getActionView();
searchView_actionBar.setOnQueryTextListener(new OnQueryTextListener() {
@Override
publicboolean onQueryTextSubmit(String query) {
returnfalse;
}
@Override
publicboolean onQueryTextChange(String newText) {
if (newText == null) {
uri_path = null;
} else {
uri_path = newText;
}
// 更新搜索过滤,重新启动Loader进行新的查询.
getLoaderManager().restartLoader(0, null, MainActivity.this);
returntrue;
}
});
returntrue;
}
}
(四)、ContentProvider中的核心代码:
publicclass MyProvider extends ContentProvider {
private MySQLiteOpenHelper dbHelper = null;
private SQLiteDatabase db = null;
privatestatic UriMatcher matcher = null;
privatestatic String AUTHORITY = "com.steven.mywordsprovider";
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, "tb_words", 1);
matcher.addURI(AUTHORITY, "tb_words/*", 2);
matcher.addURI(AUTHORITY, "tb_words/#", 3);
}
@Override
publicboolean onCreate() {
dbHelper = new MySQLiteOpenHelper(getContext());
db = dbHelper.getReadableDatabase();
returntrue;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (matcher.match(uri)) {
case 1:
Cursor cursor = db.query("tb_words", projection, selection,
selectionArgs, null, null, sortOrder);
return cursor;
case 2:
String search_str = uri.getPathSegments().get(1);
if (selection == null) {
selection = "words like '" + search_str + "%'";
} else {
selection += " and words like '" + search_str + "'%";
}
Cursor cursor2 = db.query("tb_words", projection, selection,
selectionArgs, null, null, sortOrder);
return cursor2;
default:
break;
}
returnnull;
}
@Override
public String getType(Uri uri) {
returnnull;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
returnnull;
}
@Override
publicint delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
publicint update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}