最近 在学习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三者之间内部链接实现原理 解析的更多相关文章

  1. socket,tcp,http三者之间的区别和原理

    http.TCP/IP协议与socket之间的区别下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置: 7 应用层 例如HTTP.SMTP.SNMP.FTP.Telnet.SIP ...

  2. [转]ExtJs基础--Html DOM、Ext Element及Component三者之间的区别

    要学习及应用好Ext框架,必须需要理解Html DOM.Ext Element及Component三者之间的区别. 每一个HTML页面都有一个层次分明的DOM树模型,浏览器中的所有内容都有相应的DOM ...

  3. 电脑结构和CPU、内存、硬盘三者之间的关系

    前面提到了,电脑之父——冯·诺伊曼提出了计算机的五大部件:输入设备.输出设备.存储器.运算器和控制器. 我们看一下现在我们电脑的: 键盘鼠标.显示器.机箱.音响等等. 这里显示器为比较老的CRT显示器 ...

  4. select poll epoll三者之间的比较

    一.概述 说到Linux下的IO复用,系统提供了三个系统调用,分别是select poll epoll.那么这三者之间有什么不同呢,什么时候使用三个之间的其中一个呢? 下面,我将从系统调用原型来分析其 ...

  5. 一文读懂 Spring Boot、微服务架构和大数据治理三者之间的故事

    微服务架构 微服务的诞生并非偶然,它是在互联网高速发展,技术日新月异的变化以及传统架构无法适应快速变化等多重因素的推动下诞生的产物.互联网时代的产品通常有两类特点:需求变化快和用户群体庞大,在这种情况 ...

  6. 网络互联技术(2)——前篇—【转载】电脑结构和CPU、内存、硬盘三者之间的关系

    原文链接:传送门 详细内容: 电脑结构和CPU.内存.硬盘三者之间的关系 前面提到了,电脑之父——冯·诺伊曼提出了计算机的五大部件:输入设备.输出设备.存储器.运算器和控制器. 我们看一下现在我们电脑 ...

  7. ID--HANDLE--HWND三者之间的互相转换

    利用PreTranslateMessage,响应按钮控件的按下(WM_LBUTTONDOWN)和松开(WM_LBUTTONUP)   VC的button控制只有两个事件,一个是单击事件,一个事双击事件 ...

  8. Looper: Looper,Handler,MessageQueue三者之间的联系

    在Android中每个应用的UI线程是被保护的,不能在UI线程中进行耗时的操作,其他的子线程也不能直接进行UI操作.为了达到这个目的Android设计了handler Looper这个系统框架,And ...

  9. tep环境变量、fixtures、用例三者之间的关系

    tep是一款测试工具,在pytest测试框架基础上集成了第三方包,提供项目脚手架,帮助以写Python代码方式,快速实现自动化项目落地. 在tep项目中,自动化测试用例都是放到tests目录下的,每个 ...

随机推荐

  1. 友盟统计小白教程:创建应用,申请appkey

    上回书讲到,我们已经和一个靠谱的人选择一个靠谱的统计平台注册了一个帐号,下面就该创建一个应用了. 介绍一个基础知识: appkey:友盟识别app的唯一标识,目前友盟平台上超过500000款App,每 ...

  2. centos环境下创建数据库和表的方法

    centos环境下创建数据库和表的方法 //查询数据库的命令: mysql> SHOW DATABASES; +--------------------+ | Database         ...

  3. .NET基础 (17)反射

    反射1 请解释反射的基本原理和其实现的基石2 .NET提供了哪些类型来实现反射3 如何实现动态地发射程序集4 如何利用反射来实现工厂模式 反射1 请解释反射的基本原理和其实现的基石 反射是一种动态分析 ...

  4. java程序无法启动:Unsupported major.minor version 51.0

    今天在sae上部署了一个项目,结果总是出现503错误:service unavailable,然后jvm出现了一大串错误日志,如下 JAVA_SAE_Fatal_error: Failed start ...

  5. php的循环与引用的一个坑

    上代码 $arr = array( 'a'=> 'a11', 'b'=> 'b22', 'c'=> 'c33', ); foreach ($arr as $k=>&$v ...

  6. Linux带有时间控制的多进程bash脚本

    目标 以可控制的多进程执行,达到最大执行时长后停止脚本. 思路 1.产生fifo管道,并预填充n个值(与并发数相等) 2.记录脚本本身PID并启动计时器进程(计时终止后杀脚本本身PID) 3.并发执行 ...

  7. ASP.NET 生成缩略图片类分享

    /// <summary> /// 生成图片缩略图 指定文件路径生成 /// </summary> public static void SaveImage(String fu ...

  8. Ocelot 新手上路

    新手上路,老司机请多多包含!Ocelot 在博园里文章特别多,但是按照其中一篇文章教程,如果经验很少或者小白,是没法将程序跑向博主的结果. 因此总结下     参考多篇文章,终于达到预期效果. Oce ...

  9. JS三个编码函数和net编码System.Web.HttpUtility.UrlEncode比较

    JS三个编码函数和net编码比较 总结 1.escape.encodeUri.encodeUriComponent均不会对数字.字母进行编码.2.escape:对某些字符(如中文)进行unicode编 ...

  10. [C#学习笔记]C#中的decimal类型——《CLR via C#》

    System.Decimal是非常特殊的类型.在CLR中,Decimal类型不是基元类型.这就意味着CLR没有知道如何处理Decimal的IL指令. 在文档中查看Decimal类型,可以看到它提供了一 ...