Android之Loader和LoaderManager使用
1.Loader作用
顾名思义即是指加载器,用于异步加载数据,Android 3.0 中引入了加载器,支持轻松在 Activity 或片段(Fragment)中异步加载数据;
a.在单独的线程中读取数据(耗时操作),不用阻塞UI线程的执行;
b.监听数据源是否发生变化传递新的结果(观察者模式);
2.Loader和LoaderManager使用示例
/**
* Loader测试类
*/
public class CursorLoaderActivity extends ToolbarActivity implements LoaderManager.LoaderCallbacks<Cursor>,
TextWatcher {
private static final int CURSOR_LOADER_ID = 1;//Loader的唯一标识符
private SimpleCursorAdapter adapter = null;//一个简单的适配器,用于将列从Cursor映射到XML文件中定义的TextViews或ImageViews;您可以指定您想要哪些列显示到对应的xml文件中的那个视图上;
@Bind(R.id.listView)
ListView listView;
@Bind(R.id.edit_text)
EditText edit_text;
@Override
protected int provideContentViewId() {
return R.layout.activity_cursor_loader;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterKnife.bind(this);
//绑定编辑框的文本变化事件
edit_text.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return false;
}
});
edit_text.addTextChangedListener(this);
//创建Adapter
adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_2,
null,//默认设置游标Cursor为空
new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
new int[]{android.R.id.text1, android.R.id.text2},//指定游标中的哪些列对应哪些视图控件
0);
listView.setAdapter(adapter);//设置游标适配器
// LoaderManager lm = getSupportLoaderManager();
LoaderManager lm = getLoaderManager();
//查询全部联系人
Bundle args = new Bundle();
args.putString("filter", null);
/**
* id Loader的惟一标识符(在LoaderManager容器中的唯一标识符),可以任意整型值,
* 标识符(identifier)的范围用在一个特定的LoaderManager实例中(当前Activity或者Fragment中的LoaderManager实例)。
*
* args 在创建Loader的过程中提供给Loader的可选参数。
* 如果Loader已经存在(不需要创建一个新的加载程序),则
* 参数将被忽略,最后的参数将继续使用。
*
* callback Loader中的内部接口,回调传送Loader状态的变化
*
* public abstract <D> Loader<D> initLoader(int id, Bundle args,
* LoaderManager.LoaderCallbacks<D> callback);
*/
lm.initLoader(CURSOR_LOADER_ID, args, this);//a.初始化Loader
}
/**
* 要检索联系人的哪些字段数据
*/
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
};
//b.回调接口方法
/*
* 当需要创建一个新的Loader时调用。这个例子中只有一个Loader,所以我们不关心ID。
* 首先,根据我们当前过滤是否选择使用相应的URI
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri;
String filter = args != null ? args.getString("filter") : null;
if(!TextUtils.isEmpty(filter)){
baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(filter));
}else{
baseUri = ContactsContract.Contacts.CONTENT_URI;
}
//现在创建并且返回一个CursorLoader, 它将负责为正在显示的数据创建游标。
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(this, baseUri, CONTACTS_SUMMARY_PROJECTION,
select, null, ContactsContract.Contacts.DISPLAY_NAME+" COLLATE LOCALIZED ASC");
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//切换新的cursor(游标),一旦返回,框架会关闭旧的游标
adapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
//当上面提供给onLoadFinished()的最后一个光标即将被关闭时,
//这个方法将被调用。我们需要确保我们没有使用它。
adapter.swapCursor(null);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String filter = edit_text.getText().toString();
Bundle args = new Bundle();
args.putString("filter", filter);
LoaderManager lm = getLoaderManager();
lm.restartLoader(CURSOR_LOADER_ID, args, this);
}
}
我们在AndroidManifest.xml中需要加入android.permission.READ_CONTACTS权限以读取联系人信息。
3.使用示例分析
a.lm(LoaderManager).initLoader(CURSOR_LOADER_ID, args, this);
//initLoader源码片段
public abstract <D> Loader<D> initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
initLoader方法的作用:
LoaderManager中的initLoader负责根据ID查找LoaderManager容器中是否已经存在Loader,若不存在则创建一个Loader放到LoaderManager容器中,存在与ID关联的Loader,则Loader保持不变,只是将Loader回掉callback替换成新的回调;
id:用于标识加载器(Loader)的唯一 ID。可以是任意整型值。
args:在构建时提供给加载器的可选参数(在此示例中为 null),主要用于传递参数给回调接口public Loader<Cursor> onCreateLoader(int id, Bundle args)方法使用,onCreateLoader方法被通知回调通知客户端(Activity或者Fragment)通知去创建Loader,同时回传ID和args给客户端,ID用于区分是那个Loader的回调,上面例子中只有一个Loader,则不用关心ID参数,args用于执行操作所需要的参数值,例如上面的例子参数表示查询联系人的搜索条件;
callback:给客户端(Activity或者Fragment)设置回调LoaderManager.LoaderCallbacks接口,用于监听Loader的各种状态,例如Loader创建,或者Loader加载完成等;
有时候我们可能需要重新启动的对应的ID关联的Loader,这样就可以丢弃旧的数据,调用lm.restartLoader(CURSOR_LOADER_ID, args, this);就可以完成该操作,丢弃旧的Loader,重新创建一个新的Loader;
源码分析:
每个客户端(Actvity或者Fragment)只有一个LoaderManager,而LoaderManager中会有多个Loader,如下是LoaderManager创建过程;
Activity
创建FragmentController,并返回LoaderManager
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
//通过构造函数发现FragmentController对象是单例
private FragmentController(FragmentHostCallback<?> callbacks) {
mHost = callbacks;
}
/**
* Return the LoaderManager for this activity, creating it if needed.
*/
public LoaderManager getLoaderManager() {
return mFragments.getLoaderManager();
}
FragmentController
通过代码会发现返回的是LoaderManager实现类LoaderManagerImpl
/**
* Returns a {@link LoaderManager}.
*/
public LoaderManager getLoaderManager() {
return mHost.getLoaderManagerImpl();
}
//证明客户端(Activity或者Fragment)只存在一个LoaderManager
LoaderManagerImpl getLoaderManagerImpl() {
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
return mLoaderManager;
}
不存在则创建LoaderManager实例
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap<String, LoaderManager>();
}
LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
if (lm == null && create) {
lm = new LoaderManagerImpl(who, this, started);
mAllLoaderManagers.put(who, lm);
} else if (started && lm != null && !lm.mStarted){
lm.doStart();
}
return lm;
}
LoaderManager中Loader初始化操作分析,代码如下;
LoaderManagerImpl
实际调用LoaderManagerImpl类对象的
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback)
{}方法初始化创建Loader;
LoaderInfo:用于存放创建Loader的相关信息;
首先检测是否Loader已存在
不存在则
createAndInstallLoader()创建并安装Loader
createLoader()创建Loader
callback.onCreateLoader(id, args);创建完Loader回调通知客户端(Activity或者Fragment)Loader已经创建了
installLoader()将Loader保存在LoaderManagerImpl中
mLoaders.put(info.mId, info);
开启Loader
info.start();
mLoader.startLoading();
onStartLoading();
b.回调接口方法
使用 LoaderManager 回调LoaderManager.LoaderCallbacks 是一个支持客户端与 LoaderManager 交互的回调接口。
public class CursorLoaderActivity extends ToolbarActivity implements LoaderManager.LoaderCallbacks<Cursor>,
TextWatcher {}
onCreateLoader():
当LoaderInfo被创建好以后,回调通知客户端(Activity或者Fragment)调用onCreateLoader()方法创建并返回Loader,然后将Loader赋值给LoaderInfo中的mLoader变量;
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Loader<Object> loader = callback.onCreateLoader(id, args);
info.mLoader = (Loader<Object>)loader;
public Loader<D> onCreateLoader(int id, Bundle args);
例子中创建的是CursorLoader类,CursorLoader继承自AsyncTaskLoader类;
CursorLoader一个查询ContentResolver并返回游标(Cursor)的加载器。该类以一种标准的方法来实现游标(Cursor)协议,用于查询游标(Cursor),构建在AsyncTaskLoader上,以在后台线程上执行游标查询(Cursor),从而不会阻塞应用程序的UI。
onLoadFinished()
当先前创建的加载器完成加载时,将调用此方法。该方法必须在为此加载器提供的最后一个数据释放之前调用。 此时,您应移除所有使用的旧数据(因为它们很快会被释放),但不要自行释放这些数据,因为这些数据归其加载器所有,其加载器会处理它们。
当Loader完成加载时会调用该方法回调,然后调用adapter.swapCursor(data)的切换ListView控件上显示的UI数据,同时会处理旧数据,不需要开发人员管理;
onLoaderReset()
此方法将在先前创建的加载器重置且其数据因此不可用时调用。 通过此回调,您可以了解何时将释放数据,因而能够及时移除其引用。
当上面提供给onLoadFinished()的最后一个光标即将被关闭时,这个方法将被调用。我们需要确保我们没有使用它。
adapter.swapCursor(null);
参考:
Android开发之Loader与LoaderManager
https://www.jianshu.com/p/8b8197dc2e04
https://developer.android.google.cn/guide/components/loaders
下一篇: mysql查询结果去重
推荐阅读
-
使用LoaderManager管理Loader实现异步动态加载数据
-
Android之Loader和LoaderManager使用
-
灵活使用Android中ActionBar和ViewPager切换页面
-
android开发之蜂鸣提示音和震动提示的实现原理与参考代码
-
Android 之 ContentProvider手机簿使用
-
php数组函数序列 之shuffle()和array_rand() 随机函数使用介绍_PHP教程
-
Android实现疯狂连连看游戏之加载界面图片和实现游戏Activity(四)
-
在Python操作时间和日期之asctime()方法的使用
-
android和ios调用php写的接口怎么判断用户使用的是安卓还是苹果
-
Android - Gradle 使用干货 之 生成 jar 包 和 生成混淆的 jar 包 和 上传