ContentProvider和Cursor以及CursorAdapter三者之间内部链接实现原理 解析
最近 在学习Android3.0中推出的 Loader 机制,其中CursorLoader 这个加载器说是可以实时监测数据和更新数据,为了一探究竟,就连带的将 ContentProvider和Cursor以及CursorAdapter三者间的内部交互分析了下,然而本章内容主要就是将这一块,至于Loader机制准备,下一篇来具体分析。
对于这三个类我们知道,Contentprovider就是一个Android中进程间的内容共享机制,我们可以使用ContentResolver这个工具嫁接 目标 URI 来访问对应的Contentprovider,从而获取目标Cursor数据,Android中 使用Sqliet就是这样一个机制。然而在这三个类之间其实存在了两处的观测者模式的运用。第一处在于Cursor 和 Contentprovider 之间,第二处在于 Cursor 和 CursorAdapter 之间,下面我们先来看一张时序图大致的了解下。Ps: 时序图 有哪里不对的还请及时指出啊。
上面说到观察者模式的运用 第一处在于Cursor 和 Contentprovider 之间,我们可以通过上面的时序图来加以分析,当我们通过 ContentResolver 对目标ContentProvider的数据进行CRUD(增删改查)操作时,在返回目标Cursor数据之前,我们发现在每个CRUD操作中有一个setNotifycationUri()这个方法,那么这个方法里到底做了什么呢,我们可以看看。
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
synchronized (mSelfObserverLock) {
mNotifyUri = notifyUri;
mContentResolver = cr;
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
mSelfObserver = new SelfContentObserver(this);
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
}
我们可以发现,这里它创建了一个SelfContentObserver的对象并且给它注册了Uri监听。这里SelfContentObserver看起源码知道了它继承了ContentObserver,就是一个Observer,这样一来当Uri变动时,我们就可以通知它了。注意了在我们进行CRUD操作时,我们经常会加一句 :getContext().getContentResolver().notifyChange(XXX.CONTENT_URI,null),那么这样一来Cursor类中的mSelfObserver就会收到通知并且回调onChange方法,到这里我们是不是可以看出来了这就是观察者模式的运用呢。
至于第二处则在于 Cursor 和 CursorAdapter 之间,同样的 我们也可从上面的时序图中发现。CursorAdapter中持有两个观察者:mChangeObserver和mDataSetObserver.这两个Observer在 CursorAdapter初始化时或者调用其changeCursor(Cursor c)或swapCursor(Cursor c)方法时,就被注册到Cursor中了,三种方式的代码依次如下:
void init(Context context, Cursor c, int flags) {
...省略
mCursor = c;
...省略
if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
mChangeObserver = new ChangeObserver();
mDataSetObserver = new MyDataSetObserver();
} else {
mChangeObserver = null;
mDataSetObserver = null;
} if (cursorPresent) {
if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
}
}
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
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;
}
我们可以看到,这两个Observer在初始化Adapter的时候被创建,而后会在不同情况下注册到Cursor中。这里是因为Cursor中持有两个目标对象:mContentObservable和mDataSetObservable 这两个类就继承了Observable接口。所以其实是它们两分别接受了Observer的注册。代码如下:
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
到这里第二处观察者模式运用就显示出来啦!
从时序图中,我们可以看到当Cursor类中的mSelfObserver收到通知后就会调用onChange方法
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
mContentObservable.dispatchChange(selfChange);
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
}
}
}
我们可以看到 它会触发mContentObservable这个目标对象去调用dispatchChange()方法
public void dispatchChange(boolean selfChange) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
if (!selfChange || observer.deliverSelfNotifications()) {
observer.dispatchChange(selfChange);
}
}
}
到这里,它接着就通知其注册的各个Observer去执行dispatchChange()方法,前面我们已经知道了mContentObservable了被注册了ChangeObserver 的实例 mChangeObserver,这里呢首先会执行ChangeObserver的父类ContentObserver的dispatchChange(false)方法:
public final void dispatchChange(boolean selfChange) {
if (mHandler == null) {
onChange(selfChange);
} else {
mHandler.post(new NotificationRunnable(selfChange));
}
}
接着就来到其子类实例mChangeObserver的dispatchChange()方法:
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
} @Override
public boolean deliverSelfNotifications() {
return true;
} @Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
在其Onchange()方法中调用了onContentChanged()方法:
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
mDataValid = mCursor.requery();
}
}
到这里 我们是不是恍然大悟了,mCursor.requery()则就会重新刷新并填充mCursor对象。然后还没有结束:我们的cursor重新填充了,但是不会告诉Adapter执行notifyDataSetChanged()方法,因为只有执行了这个方法,我们的界面才会刷新。
所以我们接着看下mCursor.requery()的内部做了些什么:
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
mDataSetObservable.notifyChanged();
return true;
}
我们可以看到,mDataSetObservable.notifyChanged();这个就会 会触发mDataSetObservable去通知其内部注册的observer,前面我们也讲了mDataSetObservable被注册了 CursorAdapter中的 MyDataSetObserver的实例 mDataSetObserver,所以我们接着看下mDataSetObserver的onchange()方法的实现:
private class MyDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
mDataValid = true;
notifyDataSetChanged();
} @Override
public void onInvalidated() {
mDataValid = false;
notifyDataSetInvalidated();
}
}
在该方法中调用了 notifyDataSetChanged(); 这个方法干嘛了呢,我们不仅要问,是不是它就是用来刷新界面呢?这个方法用的是子父类Baseadapter的,
BaseAdapetr:
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
我们可以看到,在其源码中,它会调用其父类BaseAdapetr中mDataSetObservable去通知其中被注册的Observer,那这个observer到底在哪里被注册的呢,这里呢 也就不饶弯子了一步到位,回到我们使用CursorAdapter的最初,但我们初始化完成它的时候,我们是不是接着会调用setAdapter()方法,将该Adapter设置到目标列表中,那么这里又做了什么呢?
public void setAdapter(ListAdapter adapter) { ...省略 if (mAdapter != null) { ...省略 mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); ...省略 requestLayout();
}
在这里我们找到了我们的答案,原来这个被注册的observer就是AdapterDataSetObserver,那这下就好啦,我们转到其内部的onchange()去一探究竟:
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount(); // Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
果不其然,原来它通过 requestLayout();来完成接下来的操作了去刷新界面,其内部就是Android中View的绘制机制了,感兴趣的话可以去了解哦!
到这里,本章内容就全部讲完啦!嘎嘎~ Ps: 有不对的还请及时指出哦!
ContentProvider和Cursor以及CursorAdapter三者之间内部链接实现原理 解析的更多相关文章
- socket,tcp,http三者之间的区别和原理
http.TCP/IP协议与socket之间的区别下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置: 7 应用层 例如HTTP.SMTP.SNMP.FTP.Telnet.SIP ...
- [转]ExtJs基础--Html DOM、Ext Element及Component三者之间的区别
要学习及应用好Ext框架,必须需要理解Html DOM.Ext Element及Component三者之间的区别. 每一个HTML页面都有一个层次分明的DOM树模型,浏览器中的所有内容都有相应的DOM ...
- 电脑结构和CPU、内存、硬盘三者之间的关系
前面提到了,电脑之父——冯·诺伊曼提出了计算机的五大部件:输入设备.输出设备.存储器.运算器和控制器. 我们看一下现在我们电脑的: 键盘鼠标.显示器.机箱.音响等等. 这里显示器为比较老的CRT显示器 ...
- select poll epoll三者之间的比较
一.概述 说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll.那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢? 下面,我将从系统调用原型来分析其 ...
- 一文读懂 Spring Boot、微服务架构和大数据治理三者之间的故事
微服务架构 微服务的诞生并非偶然,它是在互联网高速发展,技术日新月异的变化以及传统架构无法适应快速变化等多重因素的推动下诞生的产物.互联网时代的产品通常有两类特点:需求变化快和用户群体庞大,在这种情况 ...
- 网络互联技术(2)——前篇—【转载】电脑结构和CPU、内存、硬盘三者之间的关系
原文链接:传送门 详细内容: 电脑结构和CPU.内存.硬盘三者之间的关系 前面提到了,电脑之父——冯·诺伊曼提出了计算机的五大部件:输入设备.输出设备.存储器.运算器和控制器. 我们看一下现在我们电脑 ...
- ID--HANDLE--HWND三者之间的互相转换
利用PreTranslateMessage,响应按钮控件的按下(WM_LBUTTONDOWN)和松开(WM_LBUTTONUP) VC的button控制只有两个事件,一个是单击事件,一个事双击事件 ...
- Looper: Looper,Handler,MessageQueue三者之间的联系
在Android中每个应用的UI线程是被保护的,不能在UI线程中进行耗时的操作,其他的子线程也不能直接进行UI操作.为了达到这个目的Android设计了handler Looper这个系统框架,And ...
- tep环境变量、fixtures、用例三者之间的关系
tep是一款测试工具,在pytest测试框架基础上集成了第三方包,提供项目脚手架,帮助以写Python代码方式,快速实现自动化项目落地. 在tep项目中,自动化测试用例都是放到tests目录下的,每个 ...
随机推荐
- SQLServer函数 left()、charindex()、stuff()的使用
1.left() LEFT (<character_expression>, <integer_expression>) 返回character_expression 左起 ...
- 【转】C中的静态存储区和动态存储区
一.内存基本构成 可编程内存在基本上分为这样的几大部分:静态存储区.堆区和栈区.他们的功能不同,对他们使用方式也就不同. 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个 ...
- 解决同一程序在并行同时调用时,出现资源等待错误-使用DBMS_LOCK.sleep
解决同一程序被并行同时调用时,出现资源等待错误问题. 使用DBMS_LOCK.sleep (10); PROCEDURE prc_lock_test(v_engine_id in varchar, v ...
- docker怎么导出导入镜像
https://blog.csdn.net/dest_dest/article/details/80612231 把某个docker镜像保存到本地文件,命令如下docker save -o 镜像名.t ...
- 创建jdk8基础镜像
https://blog.csdn.net/qq_35981283/article/details/80738451
- oracle中的序列,可以解决自增各种编号自动增长问题,说一下我的小问题。
1.生成0001-9999的编码 //在PL/sql中 创建一个序列 Create sequence seq_cdptIncrement by 1 --自增数Start with 1 --开始累加数M ...
- leetcode 罗马数字转整数
罗马数字包含以下七种字符:I,V,X,L,C,D 和M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做II ,即为两个并列的 1.1 ...
- PARTITION BY函数
1.PARTITION BY 开窗函数, 使用场景,在合同表里,获取所有房源在最新的合同编号.或者获取每个班级每次考试的第一名. 区别聚合函数:对于每个每个分组返回多行,而聚合函数对于每个分组只返回一 ...
- Android 获取 content layout
if (findViewById(android.R.id.content) instanceof ViewGroup) { ViewGroup mainView = ((ViewGroup)find ...
- ClamAV学习【7】——病毒库文件格式学习
搜查到一份详细的ClamAV病毒文件格式资料(http://download.csdn.net/detail/betabin/4215909),英文版,国内这资料不多的感觉. 重点看了下有关PE的病毒 ...