CursorLoader为ListFragment中ListView加载数据的流程
来源:互联网 发布:2017淘宝新玩法 编辑:程序博客网 时间:2024/06/12 22:29
今天学习了下ApiDemo中的LoaderThrottle示例,内容涉及到LoaderManager、CursorLoader、ContentProvider等几个重要类,值得个人学习。
现将个人的学习心得分享出来,其中难免有理解上的误区,希望大家多多提出意见。
Android 3.0之后引入了Loaders类,为Activity和Fragment提供了一种很好的异步数据加载机制,详细的介绍大家可用参考官方文档进一步学习。
Activity和Fragment通过LoaderManager来对Loaders进行管理,涉及到的主要类和接口包含LoaderManager、LoaderManager.LoaderCallbacks、Loader、AsyncTaskLoader和CursorLoader。其中CursorLoader继承自AsyncTaskLoader,而AsyncTaskLoader是Loader的直接抽象子类。
我们知道,ListFragment中内嵌了一个ListView,我们可用直接调用ListFragment#setListAdapter方法来绑定Adapter。现在我们来看Demo中的主要代码:
1、自定义一个ListFragment,并实现LoaderManager.LoaderCallbacks接口:
public static class ThrottledLoaderListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {//代码参考下文}
在这个类中,我们主要在onActivityCreated方法中进行核心代码的初始化:
setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1, null, new String[] { MainTable.COLUMN_NAME_DATA }, new int[] { android.R.id.text1 }, 0); setListAdapter(mAdapter); // Start out with a progress indicator. setListShown(false); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this);
注意上面代码片段中最后一行代码
getLoaderManager().initLoader(0, null, this);这行代码可用确保对Loader进行初始化并将其激活。在初始化的时候,会调用LoaderManager.LoaderCallbacks的两个主要方法:
public Loader<Cursor> onCreateLoader(int id, Bundle args) { CursorLoader cl = new CursorLoader(getActivity(), MainTable.CONTENT_URI, PROJECTION, null, null, null); cl.setUpdateThrottle(2000); // update at most every 2 seconds. return cl; }
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { mAdapter.swapCursor(data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } }
在调用onCreateLoader方法创建Loader'成功后,当Loader数据加载完成,会自动调用onLoadFinished方法。在这个方法中,核心代码是
mAdapter.swapCursor(data);这行代码其实就是CursorLoader与ListFragment通信的关键。我们来看其实现:
@Override public Cursor swapCursor(Cursor c) { Cursor res = super.swapCursor(c); // rescan columns in case cursor layout is different findColumns(mOriginalFrom); return res; }
这个方法实现很简单,关键部分是super.swapCursor(c)方法的调用:
public Cursor swapCursor(Cursor newCursor) { if (newCursor == mCursor) { return null; } Cursor oldCursor = mCursor; if (oldCursor != null) { if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); } mCursor = newCursor; if (newCursor != null) { if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataSetChanged(); } else { mRowIDColumn = -1; mDataValid = false; // notify the observers about the lack of a data set notifyDataSetInvalidated(); } return oldCursor; }
针对Demo的实现,我们主要关心的还是这行代码:
// notify the observers about the new cursor notifyDataSetChanged();我们知道,这个方法其实就是CursorAdapter通知ListView对数据进行刷新。
到这里,我们就应该知道了CursorLoader是如何通知ListFragment来刷新界面的了。
2、现在还有一个问题需要说明下,那就是在Demo中,当向ContentProvider中添加数据时,CursorLoader是如何知道数据发生了变化并通知ListView刷新界面的呢?
在菜单点击动作中,有这样一段代码:
final ContentResolver cr = getActivity().getContentResolver();//此处省略部分代码case POPULATE_ID: if (mPopulatingTask != null) { mPopulatingTask.cancel(false); } mPopulatingTask = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { for (char c='Z'; c>='A'; c--) { if (isCancelled()) { break; } StringBuilder builder = new StringBuilder("Data "); builder.append(c); ContentValues values = new ContentValues(); values.put(MainTable.COLUMN_NAME_DATA, builder.toString()); cr.insert(MainTable.CONTENT_URI, values); // Wait a bit between each insert. try { Thread.sleep(250); } catch (InterruptedException e) { } } return null; } }; mPopulatingTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); return true;
我们注意到,在这段代码中,程序调用了ContentResolver的insert方法。该方法其实是对ContentProvider中insert方法的调用。我们来看下Demo中SimpleProvider(继承自ContentProvider)中insert方法(部分):
SQLiteDatabase db = mOpenHelper.getWritableDatabase(); long rowId = db.insert(MainTable.TABLE_NAME, null, values); // If the insert succeeded, the row ID exists. if (rowId > 0) { Uri noteUri = ContentUris.withAppendedId(MainTable.CONTENT_ID_URI_BASE, rowId); getContext().getContentResolver().notifyChange(noteUri, null); return noteUri; }
上面代码中,我们注意这行代码:
getContext().getContentResolver().notifyChange(noteUri, null);我们查看文档中对notifyChange介绍就知道,它实际上是告诉ContentObserver(第二个参数)对应的URI(第一个参数)发生了变化。然而,在这里,ContentObserver却为null。那么,当URI发生变化,ListFragment又是如何知道的呢?
其实,在Demo中,SimpleProvider的query方法已经告诉了我们:
@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();queryBuilder.setTables(MainTable.TABLE_NAME);switch(uriMatcher.match(uri)) {case MAIN: queryBuilder.setProjectionMap(mainProjectionMap);break;case MAIN_ID: queryBuilder.setProjectionMap(mainProjectionMap);queryBuilder.appendWhere(MainTable._ID + " =?");selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});break;}if(TextUtils.isEmpty(sortOrder)){sortOrder = MainTable.DEFAULT_SORT_ORDER;}SQLiteDatabase db = dbHelper.getWritableDatabase();Cursor c = queryBuilder.query(db, projection, selection, selectionArgs, null /* no group */, null /* no filter */, sortOrder);c.setNotificationUri(getContext().getContentResolver(), uri);return c;}
倒数第二行代码已经很明显了:
c.setNotificationUri(getContext().getContentResolver(), uri);这行代码告诉ContentResolver上的监听器,当URI变化时要做相应的更新。
3、大家可能觉得调用过程已经很清晰了,可是,你搜遍整个Demo代码,你会发现,没有找到对SimpleProvider中query方法的调用。既然这样,上面的代码片段岂不是根本就不可能执行吗,又怎么能监听到数据变化呢?
其实,我们忽略了一个地方,那就是LoaderManager。通过断点调试,我们可用看到,实际上LoaderManager调用了CursorLoader,而CursorLoader调用了这里的query方法:
由于本人菜鸟一枚,关于应用如果管理LoaderManager,LoaderManager是在什么时候调用了CursorLoader,我也没搞懂,有知道的朋友欢迎留言指出,在此感激不尽。
总结:本人的表达很是欠佳,可能很多地方说的不够周密。在此,对本文作个简单总结,希望大家能够不吝赐教。
LoaderManager调用了CursorLoader,CursorLoader在初始化成功后,会调用绑定的ContentProvider的query方法,进而对URI的变化注册监听器。当URI变化时,CursorLoader会调用CursorAdapter的sawpCursor方法,从而通知ListFragment更新UI界面。
PS:LoaderThrottle中涉及到SQLiteDatabase、ContentProvider、Loader的使用,强烈建议大家去研究研究。源码路径:导入ApiDemo后,在com.example.android.apis.app包下。
- CursorLoader为ListFragment中ListView加载数据的流程
- Android ListView 正在加载 异步载入数据 CursorLoader 例子
- 使用CursorLoader异步加载数据
- 使用CursorLoader异步加载数据
- 关于listfragment中使用listview的点击事件不响应
- 解决ListFragment中getListView()为空的方法
- Android之cursorLoader进行数据异步加载
- ListView中加载数据
- 通过 LoaderManager + CursorLoader加载通讯录中所有人
- Loader之CursorLoader的使用——加载系统短信的数据(1)
- listview的数据加载
- android中listview的数据的异步加载
- android中ListView的分页加载数据实现
- android中关于ListView分页加载数据的分析
- Android中listview加载数据的时候出现空白页
- ListView中数据的分批及分页加载
- ListView中adapter加载数据的两种方式
- ListView的分批加载数据
- openlayers 多边形重叠判断平移
- iis6.0的网站建设及权限设置
- poj3461
- 儿童卡通城堡banner动画
- ffmpeg 从内存中读取数据
- CursorLoader为ListFragment中ListView加载数据的流程
- 开发过程中出现的BUG集锦
- no talloc stackframe at ../source3/param/loadparm.c
- extern“c”
- CentOS 6.0安装phpMyAdmin的简单步骤
- 静态类
- week 11 lianxi
- [编译错误:warning C4005]ws2def.h(91): warning C4005: “AF_IPX”: 宏重定义 winsock.h(460) : 参见“AF_IPX”的前一个定义
- Asp.net中使用iframe动态加载页面