GVKun编程网logo

FZU - 2103 Bin & Jing in wonderland

20

如果您对FZU-2103Bin&Jinginwonderland感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解FZU-2103Bin&Jinginwonderland的各种细节,此外还有关于1

如果您对FZU - 2103 Bin & Jing in wonderland感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解FZU - 2103 Bin & Jing in wonderland的各种细节,此外还有关于10.Nodes and Bindings、Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?、Android Data Binding 系列 (二) -- Binding 与 Observer 实现原理、android.databinding.BindingConversion的实例源码的实用技巧。

本文目录一览:

FZU - 2103 Bin & Jing in wonderland

FZU - 2103 Bin & Jing in wonderland

@H_301_5@FZU - 2103 Bin & Jing in wonderland

  题目大意:有n个礼物,每次得到第i个礼物的概率是p[i],一个人一共得到了k个礼物,然后按编号排序后挑选出r个编号最大的礼物。现在给出r个礼物的编号,问能得到这r个礼物的概率。

  首先剩下的k-r个礼物中的编号肯定不能大于r个礼物中最小的编号id,我们就根据id把礼物分成两部分,一部分就是编号大于Id的礼物的,另一部分就是剩下的。对于编号大于id的礼物选取的概率,当前剩余nk1个空位,那么就是挑选num[i]个位置来放它,C[nk1][num[i]*pow(p[i],num[i]),然后概率累乘。而剩下的部分,空位有nk2=k-r+num[id]个,小于id是礼物概率之和是pn,那么我们枚举id礼物有i个,小于id的就有nk2-i个,第一个可以在[1,id)内任意取,第二个也是一样。。这部分概率就是pow(pn,nk2-i),那id礼物有i个这种情况下的概率就是C[nk2][i]*pow(p[id],i)*pow(pn,nk2-i),然后概率累加。最后两部分的概率相乘就是最终答案。

分享图片

分享图片

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 typedef long long ll;
 5 int num[25];
 6 ll C[55][55];
 7 double p[25];
 8 void init()
 9 {
10     for(int i=0;i<=52;i++)
11     {
12         C[i][0]=1ll;
13         for(int j=1;j<=i;j++)
14         {
15             if(j<=i/2)
16                 C[i][j]=C[i-1][j-1]+C[i-1][j];
17             else
18                 C[i][j]=C[i][i-j];
19         }
20     }
21 } 
22 double pow(double x,int y)
23 {
24     double ans=1.0;
25     while(y)
26     {
27         if(y&1)
28             ans*=x;
29         x*=x;
30         y>>=1;
31     }
32     return ans;
33 }
34 int main()
35 {
36     init();
37     int t,n,k,r;
38     scanf("%d",&t);
39     while(t--)
40     {
41         scanf("%d%d%d",&n,&k,&r);
42         for(int i=1;i<=n;i++)
43         {
44             scanf("%lf",&p[i]);
45             num[i]=0;
46         }
47         int id=n+1,x;
48         for(int i=1;i<=r;i++)
49         {
50             scanf("%d",&x);
51             id=min(id,x);
52             num[x]++;
53         }
54         int nk1=k,nk2=k-r+num[id];
55         double ans1=1.0,ans2=0.0,pn=0.0;
56         for(int i=id+1;i<=n;i++)
57         {
58             if(num[i])
59             {
60                 ans1*=1.0*C[nk1][num[i]]*pow(p[i],num[i]);
61                 nk1-=num[i];//用了num[i]个位置,减去 
62             }
63         }
64         for(int i=1;i<id;i++)
65             pn+=p[i];
66         for(int i=num[id];i<=nk2;i++)
67             ans2+=1.0*C[nk2][i]*pow(p[id],nk2-i);
68         printf("%.6f\n",ans1*ans2);
69     }
70     return 0;
71 }
看概率过

10.Nodes and Bindings

10.Nodes and Bindings

节点数据绑定

 

节点是构成Ventuz场景的基本元素。每个节点既属于图层、也属于层级或内容。既可以在图层编辑器,也可以在层级编辑器或内容编辑器中编辑。

 

内容节点包括资产描述(如材质、xml文件等)、数字常量、事件或不直接影响渲染的项目。而节点像一个ventuz渲染引擎指令。例如激活某个材料或材质去渲染。图层节点比较特殊,在2D图层上的所有内容都会被渲染到专用的材质上。在图层上使用的混合、遮罩、调色等效果都会与其它图层合成在一块呈现最终的效果。

 

内容节点可以在场景的任何位置使用,但层次和层次节点只能在3D图层中使用。

 

内容节点或层次节点可以打开Toolboxes工具,拖拽想要的内容到相应的编辑器中。或者单击”空格键”,打开Fast Toolbox工具,拖拽想要的内容到相应的编辑器中。

 

1、绑定属性值

   每个节点都有多个属性值,这些属性值会影响节点的行为,如矩形的大小或视频的播放速度。每个节点有多个输出属性,这些输出属性可以被其它节点的输入属性使用。把其它节点的输入属性,直接拖拽到输出属性上,就完成了属性的绑定。当该节点的输出属性发生变化,被绑定到输出属性的输入属性值也会发生变化。例如把x-Rotation的输出属性绑定到移动器节点的输入属性上,移动器上的对象都会围绕x-轴连续旋转。

 

 

内容节点属性被绑定后,在内容编辑器中会显示连接节点的箭头。如果要创建绑定,请选择一个节点,打开该节点的属性编辑器,把该节点的一个输入属性拖拽(一定要拖拽属性名称)到另一个节点上,释放鼠标后,Ventuz将打开第二个节点的输出属性列表,从中选择一个属性后,将创建一个连接。箭头方向是从输出节点到输入节点。

 

单击表示绑定的箭头可以删除其中的绑定。同时单击表示绑定的箭头还可以出现一个所有绑定的列表,单击任何列表前的任何一个“Q”,都可以删除该绑定。

 

2、图层到图层的绑定

 

属性可以跨图层进行绑定。可以把属性从一个图层绑定到另一个图层,在绑定之前,需要把该图层内容节点设置为expose。下面的截图中显示可一个触摸按钮,该触摸按钮从3D图层暴露出来,并绑定2D电影剪辑层用来控制播放进度。

 

 

Inside the Layer3D - exposed the SingleTap

 

 

Layer Level - The exposed SingleTap is visible and can be bound

 

 

Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?

Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?


目录:

  • Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?

  • Android : 跟我学Binder --- (2) AIDL分析及手动实现

  • Android : 跟我学Binder --- (3) C程序示例

  • Android : 跟我学Binder --- (4) 驱动情景分析

  • Android : 跟我学Binder --- (5) C++实现

  • Android : 跟我学Binder --- (6)  JAVA实现

 

 

一、引言

  如果把Android系统比作一幅精美绝伦的画,那Binder则是其浓墨重彩的独特一笔。初步了解过的人应该知道Binder是Android核心进程间通信(IPC:Internet Process Connection)手段之一,它是基于开源的 OpenBinder 实现,OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之实现通信。而日常开发中涉及到的如:AIDL、插件化编程技术 等等,底层通信实现都离不开Binder,所以如果想在“Android系统开发工程师”的头衔前面加上“高级”两个字,那理解、掌握Binder机制则是必经之路。

 

二、Linux 下传统的进程间通信原理

  首先了解一下 Linux IPC 相关的概念和原理有助于理解 Binder 通信原理。

  1.Linux 中跨进程通信涉及到的一些基本概念:

    进程隔离进程间不可直接相互访问资源

      简单的说就是操作系统中,进程与进程间内存是不共享的,两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。

 

    ②进程空间划分:用户空间(User Space)/内核空间(Kernel Space)

      现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间,它们在自己的空间运行,相互隔离。

 

    ③系统调用:用户态/内核态

      虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态),执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。当进程在执行用户自己的代码的时候,则称其处于用户运行态(用户态)。系统调用主要通过如下两个函数来实现,在编写Linux设备驱动程序时经常用到:

      copy_from_user() //将数据从用户空间拷贝到内核空间
      copy_to_user() //将数据从内核空间拷贝到用户空间

  2.Linux 下的传统 IPC 通信原理:

    Linux的IPC通常做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输。

  但是这种传统的 IPC 通信方式有两个显著缺点:

  (1)一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝,效率低,可能此时会想到共享内存方式,但是其难于控制不稳定;

  (2)接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,对于手机这种资源紧张的嵌入式移动设备来说无疑是巨大负担;

  可见传统IPC效率低,占资源,除此之外还有安全、稳定性等缺点不足以胜任Android的核心进程间通信方式,下面正式介绍Binder机制,看其有何过人之处。

 

三、为何要用Binder通信机制?

  Android系统的内核Linux已经有很多进程间通信的方式,比如:管道(Pipe)、信号(Signal)和跟踪(Trace)、插口(Socket)、报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)等 IPC 机制,那Android为何还要再实现一个Binder IPC 呢?主要是基于高效性稳定性安全性几方面原因。

  1.高效对于手机移动通信设备来说,是基于Client-Server即C/S架构的通信方式,而上面提到的Linux传统IPC中只有socket支持Client-Server的通信方式,但是socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。

IPC方式 数据拷贝次数
共享内存 0
Binder 1
Socket/管道/消息队列 2

  2.稳定性Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。所以从稳定性的角度讲,Binder 机制是也优于内存共享的复杂控制缺点。

  3.安全Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

    

  Binder优势总结:

优势 描述
性能 只需要一次数据拷贝,性能上仅次于共享内存
稳定性 基于 C/S 架构,职责明确、架构清晰,因此稳定性好
安全性 为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志

 

 

  另外上面讲过Linux IPC的通信原理,现在正式介绍 Binder IPC 的通信原理

  正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信,此内核模块就叫 Binder 驱动(Binder Dirver)。

  那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。这就不得说到 Linux 下的另一个概念:内存映射(mmap):将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间,内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。

  但是 mmap() 通常是用在有物理介质的文件系统上的,比如对flash的操作,因为进程中的用户区域是不能直接和物理设备打交道的,如果想要把flash上的数据读取到进程的用户区域,需要两次拷贝(flash-->内核空间-->用户空间),通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。mmap()使用方法如下:

/*fd描述为打开的/dev/binder文件句柄;
 *mapsize为映射内存区域的大小;
 *mapped为映射到用户空间的内存起始地址;
 */
 bs->fd = open("/dev/binder", O_RDWR);
 bs->mapsize = mapsize;
 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

 一次完整的 Binder IPC 通信过程通常是这样:

  ①首先 Binder 驱动在内核空间创建一个数据接收缓存区;

  ②接着在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;

  ③发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间;

 

四、Binder通信模型实现

  通过介绍、对比Android Binder IPC和Linux 传统IPC之间的差异,Binder IPC的各优点已经凸显出来。一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。接下来看看实现层面是如何设计的?

 1. Client/Server/ServiceManager/驱动

  前面介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

  Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 www.google.com 对应的服务器。下面是Binder类比介绍:

Binder 驱动
  Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。 

ServiceManager 与实名 Binder
  ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了有 IP 地址外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。
  细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋,ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

Client 获得实名 Binder 的引用
  Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

至此,大致能总结出 Binder 通信过程:

  ①首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;

  ②Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。

  ③Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

 

 2. Binder 通信中的代理模式

  目前已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题比较困惑:A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

  前面介绍跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己,当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

   到此可以对 Binder 做个更加全面的定义了:

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
  • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
  • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理;
  • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象,Binder 驱动会对这个跨越进程边界的对象做一点点特殊处理,自动完成代理对象和本地对象之间的转换。

 

 

  可以参考该博客自测对binder机制的理解程度:https://www.jianshu.com/p/adaa1a39a274

 

-end-

Android Data Binding 系列 (二) -- Binding 与 Observer 实现原理

Android Data Binding 系列 (二) -- Binding 与 Observer 实现原理

写在前面

上篇文章 Android Data Binding 系列 (一) -- 详细介绍与使用 介绍了 Data Binding 的基础及其用法,本文接上篇,结合 DataBindingDemo 来学习下 Data Binding 的实现。

绑定实现

Activity 在 inflate layout 时,通过 DataBindingUtil 来生成绑定,从代码看,是遍历 contentView 得到 View 数组对象,然后通过数据绑定 library 生成对应的 Binding 类,含 Views、变量、listeners 等。生成类位于 build/intermediates/classes/debug/...package.../databinding/xxx.java 下,具体如何生成这里暂不作深入。

绑定过程

  • 首先,会在父类(ViewDataBinding)中实例化回调或 Handler,用于之后的绑定操作;
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;

if (USE_CHOREOGRAPHER) {
    mChoreographer = Choreographer.getInstance();
    mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            mRebindRunnable.run();
        }
    };
} else {
    mFrameCallback = null;
    mUIThreadHandler = new Handler(Looper.myLooper());
}
  • 接着,通过调用 mapBindings(...) 遍历布局以获得包含 bound、includes、ID Views 的数组对象,再依次赋给对应 View
final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
  • 然后,调用 invalidateAll() -> requestRebind() -> ... -> mRebindRunnable.run() - 执行 Runnable
// 用于动态重新绑定 Views
private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        .....
        executePendingBindings();
    }
};
  • 最后,通过该 Runnable 会执行到 executePendingBindings() -> ... -> executeBindings(),在这里会执行绑定相关操作。
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;   // mDirtyFlags 变量更新的标志
        mDirtyFlags = 0;
    }
    .....
}

设置变量 (数据对象)

普通 Java bean 对象

  • 首先,通过 mDirtyFlags 标识变量 (所有变量共用)
synchronized(this) {
    mDirtyFlags |= 0x1L;
}
  • 然后,调用 notifyPropertyChanged(...) 来通知更新(若有回调)
public void notifyPropertyChanged(int fieldId) {
    if (mCallbacks != null) {
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
}
  • 最后,调用 requestRebind() -> ... -> executeBindings() 再次执行绑定操作,将数据更新到 Views 上
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    .....
}

Observable 对象

  • 在设置变量时,会先调用 updateRegistration(..) 注册一个 Observable 对象的监听
public void setContact(com.connorlin.databinding.model.ObservableContact contact) {
    updateRegistration(0, contact);
    this.mContact = contact;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.contact);
    super.requestRebind();
}
  • 其他步骤同普通 Java bean 对象

ObservableFields 对象

  • 前期步骤同普通 Java Bean 对象

  • 与 Observable 对象不同的是,Observable 对象的监听是在 executeBindings() 中注册的

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    ...
    if ((dirtyFlags & 0xfL) != 0) {
        if ((dirtyFlags & 0xdL) != 0) {
            if (contact != null) {
                // read contact.mName
                mNameContact = contact.mName;
            }
            updateRegistration(0, mNameContact);

            if (mNameContact != null) {
                // read contact.mName.get()
                mNameContact1 = mNameContact.get();
            }
        }
        ...
    }
    ...
}

注册 Observable 对象监听

  • 入口 updateRegistration(0, contact)
protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}

private boolean updateRegistration(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    ...
    // 确保不重复监听,先移除再添加观察监听
    unregisterFrom(localFieldId);
    registerTo(localFieldId, observable, listenerCreator);
    return true;
}

protected void registerTo(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return;
    }

    // 创建对象监听并存到mLocalFieldObservers中
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        // CREATE_PROPERTY_LISTENER -> create(...)
        listener = listenerCreator.create(this, localFieldId);
        mLocalFieldObservers[localFieldId] = listener;
    }

    // 将监听绑定到Observable对象上
    listener.setTarget(observable);
}

每个 Observable 对象都会添加一个观察监听,保存在数组 mLocalFieldObservers 中,并以 localFieldId 索引。

  • CREATE_PROPERTY_LISTENER 为何物?
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
    @Override
    public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
        // 返回从WeakPropertyListener实例中获取的监听器(WeakListener)
        return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
    }
}

private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
        implements ObservableReference<Observable> {
    final WeakListener<Observable> mListener;

    public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
        mListener = new WeakListener<Observable>(binder, localFieldId, this);
    }

    @Override
    public WeakListener<Observable> getListener() {
        return mListener;
    }

    @Override
    public void addListener(Observable target) {
        // WeakPropertyListener 继承于 Observable.OnPropertyChangedCallback,
        // 所以 this 其实就是 Observable对象的属性监听器
        target.addOnPropertyChangedCallback(this);
    }

    ...
}

private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
    private final ObservableReference<T> mObservable;
    protected final int mLocalFieldId;
    private T mTarget;

    ...

    public void setTarget(T object) {
        unregister();
        mTarget = object;
        if (mTarget != null) {
            // mObservable 是上面的 WeakPropertyListener对象
            // mTarget 是绑定到listener上得Observable对象
            mObservable.addListener(mTarget);
        }
    }

    ...
}

CREATE_PROPERTY_LISTENER 实际上只是一个接口实例,注册时会调用它的 create() 方法创建一个弱引用 listener,它的作用是将 listener 绑定到 Observable 对象上, 绑定时,会调用 listener.setTarget(...) 将 Observable 对象传给 WeakPropertyListener 实例,然后,WeakPropertyListener 会为 Observable 对象添加 OnPropertyChangedCallback

  • addOnPropertyChangedCallback 实现

addOnPropertyChangedCallback 在 BaseObservable 中实现,首先会实例化一个 PropertyChangeRegistry 对象,同时创建一个用来通知 Observable 对象重新绑定更新的回调 CallbackRegistry.NotifierCallback。然后将 OnPropertyChangedCallback 添加到 PropertyChangeRegistry 的回调列表中

@Override
public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
    if (mCallbacks == null) {
        mCallbacks = new PropertyChangeRegistry();
    }
    mCallbacks.add(callback);
}

这样,注册 Observable 对象的监听就完毕了。

更新 (重新绑定) Observable 对象

设置或更新 Observable 对象时都会调用 notifyPropertyChanged()notifyChange() 来通知更新,那到底是如何更新的呢?

  • 回调过程
public void notifyPropertyChanged(int fieldId) {
    // mCallbacks 是 PropertyChangeRegistry对象,在 addOnPropertyChangedCallback 时实例化
    // 如果注册了Observable对象监听,那么mCallbacks不为null
    if (mCallbacks != null) {
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
}

// baseLibrary
private void notifyCallbacks(T sender, int arg, A arg2, int startIndex, int endIndex, long bits) {
    long bitMask = 1L;
    for(int i = startIndex; i < endIndex; ++i) {
        if((bits & bitMask) == 0L) {
            // mNotifier 是实例化PropertyChangeRegistry时创建的
            // mNotifier 即 CallbackRegistry.NotifierCallback
            this.mNotifier.onNotifyCallback(this.mCallbacks.get(i), sender, arg, arg2);
        }
        bitMask <<= 1;
    }
}

// PropertyChangeRegistry.NOTIFIER_CALLBACK
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
        int arg, Void notUsed) {
    // callback 是为Observable对象添加的OnPropertyChangedCallback,即WeakPropertyListener
    callback.onPropertyChanged(sender, arg);
}

// WeakPropertyListener
public void onPropertyChanged(Observable sender, int propertyId) {
    // binder 即生成的Binding类对象
    ViewDataBinding binder = mListener.getBinder();
    ...
    binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}

private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
    // onFieldChange 实现在生成的Binding类中
    boolean result = onFieldChange(mLocalFieldId, object, fieldId);
    if (result) {
        // 如果对象属性变化,将重新绑定
        requestRebind();
    }
}

通过 notifyPropertyChanged 调用到 mNotifier 回调, mNotifier 通知 OnPropertyChangedCallback Observable 对象属性发生变化,然后在 onPropertyChanged 中又转给 ViewDataBinding 对象 (生成的 Binding 类) 处理。

  • 判断是否需要重新绑定并执行,在生成的 Binding 类中实现
// 生成的Binding类中得方法
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
    // 如果变量不是Observable类型或没有添加 Bindable注解,就不会判断,直接返回false
    switch (localFieldId) {
        case 0 :
            return onChangeContact((com.connorlin.databinding.model.ObservableContact) object, fieldId);
    }
    return false;
}

private boolean onChangeContact(com.connorlin.databinding.model.ObservableContact contact, int fieldId) {
    switch (fieldId) {
        case BR.name: {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;// 通过mDirtyFlags判断对象是否变化
            }
            return true;
        }
        ...
    }
    return false;
}

至此,更新过程完毕。

整个注册与更新过程可以用一张流程图来概括:

事件处理

事件处理的原理很简单,在生成 Binding 类中会实现 View 事件的监听,在构造时实例化 View 的事件监听,然后在绑定时将事件监听对象赋值给对应 View,这样,点击时就会触发相应的监听。

这里以 DataBindingDemo 中 EventActivity 部分为例:

  • 生成的 Binding 类并实现 View 的事件监听
public class ActivityEventBinding extends android.databinding.ViewDataBinding
    implements android.databinding.generated.callback.OnCheckedChangeListener.Listener,
        android.databinding.generated.callback.OnClickListener.Listener {
    // Checkbox check监听
    private final android.widget.CompoundButton.OnCheckedChangeListener mCallback3;
    private final android.view.View.OnClickListener mCallback2;
    private final android.view.View.OnClickListener mCallback1;
    // listeners
    private OnClickListenerImpl mAndroidViewViewOnCl;
    ...
    // Listener Stub Implementations
    public static class OnClickListenerImpl implements android.view.View.OnClickListener{
        private com.connorlin.databinding.handler.EventHandler value;
        public OnClickListenerImpl setValue(com.connorlin.databinding.handler.EventHandler value) {
            this.value = value;
            return value == null ? null : this;
        }
        @Override
        public void onClick(android.view.View arg0) {
            this.value.onClickFriend(arg0);
        }
    }
    ...
}
  • 实例化 View 的事件监听
public ActivityEventBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    ...
    // listeners
    mCallback3 = new android.databinding.generated.callback.OnCheckedChangeListener(this, 3);
    mCallback2 = new android.databinding.generated.callback.OnClickListener(this, 2);
    mCallback1 = new android.databinding.generated.callback.OnClickListener(this, 1);
    invalidateAll();
}
  • 在执行绑定中绑定 View 事件监听
@Override
protected void executeBindings() {
    ...
    if ((dirtyFlags & 0x6L) != 0) {
        if (handler != null) {
            // read handler::onClickFriend
            androidViewViewOnCli = (((mAndroidViewViewOnCl == null)
                ? (mAndroidViewViewOnCl = new OnClickListenerImpl()) : mAndroidViewViewOnCl).setValue(handler));
        }
    }
    // batch finished
    if ((dirtyFlags & 0x6L) != 0) {
        this.mboundView1.setOnClickListener(androidViewViewOnCli);
    }
    if ((dirtyFlags & 0x4L) != 0) {
        this.mboundView2.setOnClickListener(mCallback1);
        this.mboundView3.setOnClickListener(mCallback2);
        android.databinding.adapters.CompoundButtonBindingAdapter.setListeners(
            this.mboundView4, mCallback3, (android.databinding.InverseBindingListener)null);
    }
}
  • 触发事件并执行

ViewStub

原理类似,只是利用 ViewStubProxy 来延迟绑定。

  • 使用 layout 中的 ViewStub 实例化一个 ViewStubProxy 对象赋给 viewstub 变量,并与 Bingding 关联
public ActivityViewStubBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds);
    ...
    this.viewStub = new android.databinding.ViewStubProxy((android.view.ViewStub) bindings[1]);
    this.viewStub.setContainingBinding(this);
    ...
}
  • 实例化 ViewStubProxy 的同时会注册 inflate 监听
private OnInflateListener mProxyListener = new OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        mRoot = inflated;
        mViewDataBinding = DataBindingUtil.bind(mContainingBinding.mBindingComponent,
                inflated, stub.getLayoutResource());
        mViewStub = null;

        if (mOnInflateListener != null) {
            mOnInflateListener.onInflate(stub, inflated);
            mOnInflateListener = null;
        }
        mContainingBinding.invalidateAll();
        mContainingBinding.forceExecuteBindings();
    }
};

public ViewStubProxy(ViewStub viewStub) {
    mViewStub = viewStub;
    mViewStub.setOnInflateListener(mProxyListener);
}
  • inflate ViewStub
if (!mActivityViewStubBinding.viewStub.isInflated()) {
    mActivityViewStubBinding.viewStub.getViewStub().inflate();
}

当 ViewStub infate 时,执行 mProxyListener,其中会生成 ViewStub 的 Binding,并强制执行主 Binding 重绑

  • 绑定 ViewStub
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    // batch finished
    if (viewStub.getBinding() != null) {
        viewStub.getBinding().executePendingBindings();
    }
}

这样,ViewStub 绑定就结束了。

本篇完,敬请期待下篇...


我的简书账号是 ConnorLin,欢迎关注!

我的简书专题是 Android 开发技术分享,欢迎关注!

我的个人博客 欢迎关注!

原创文章,欢迎转载,转载请注明出处!

欢迎您扫一扫上面的微信公众号,订阅我的博客!

android.databinding.BindingConversion的实例源码

android.databinding.BindingConversion的实例源码

项目:moserp    文件:Quantity.java   
@BindingConversion
public static String convertQuantityToString(Quantity quantity) {
    Log.d("Quantity","convert quantity to string " + quantity);
    if (quantity == null) {
        return null;
    }
    return quantity.toString();
}
项目:moserp    文件:Quantity.java   
@BindingConversion
public static Quantity convertStringToQuantity(String string) {
    Log.d("Quantity","convert string to quantity " + string);
    if (string == null) {
        return Quantity.ZERO;
    }
    return new Quantity(string);
}
项目:kickmaterial    文件:BindingAdapters.java   
/**
 * Allows to pass additional parameter (click listener)
 * to adapter created by {@link me.tatarka.bindingcollectionadapter.BindingRecyclerViewAdapters}
 *
 * @param clickListener click listener to be bound to all items.
 * @return
 */
@BindingConversion
public static BindingRecyclerViewAdapterFactory toRecyclerViewAdapterFactory(final CategoryClickListener clickListener) {
    return new BindingRecyclerViewAdapterFactory() {
        public <T> BindingRecyclerViewAdapter<T> create(RecyclerView recyclerView,ItemViewArg<T> arg) {
            return new CategoriesRecyclerViewAdapter<>(arg,clickListener);
        }
    };
}
项目:onyxbeacon-android-sdk    文件:BindingAdapters.java   
/**
 * Allows to pass additional parameter (click listener)
 * to adapter created by {@link me.tatarka.bindingcollectionadapter.BindingRecyclerViewAdapters}
 *
 * @param clickListener click listener to be bound to all items.
 * @return
 */
@BindingConversion
public static BindingRecyclerViewAdapterFactory toRecyclerViewAdapterFactory(final CategoryClickListener clickListener) {
    return new BindingRecyclerViewAdapterFactory() {
        public <T> BindingRecyclerViewAdapter<T> create(RecyclerView recyclerView,clickListener);
        }
    };
}
项目:android-advanced-light    文件:Utils.java   
@BindingConversion
public static String convertDate(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(date);
}
项目:MVVM-JueJin    文件:RecyclerViewAdapter.java   
@BindingConversion
public static ItemView resToResHolder(@LayoutRes int layoutRes) {
    int defaultBindingVariable = BR.item;
    return ItemView.of(defaultBindingVariable,layoutRes);
}
项目:chameleon-live-wallpaper    文件:Util.java   
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return color != 0 ? new ColorDrawable(color) : null;
}
项目:Dahaka    文件:BindingAdapter.java   
@BindingConversion
public static int convertBooleanToVisibility(boolean visible) {
    return visible ? View.VISIBLE : View.GONE;
}
项目:DereHelper    文件:BindingAdapter.java   
@BindingConversion
public static String datetoString(Date date) {
    return Utils.formatDate(date);
}
项目:DereHelper    文件:BindingAdapter.java   
@BindingConversion
public static String intToString(int integer) {
    return integer == 0 ? "" : String.valueOf(integer);
}
项目:DereHelper    文件:BindingAdapter.java   
@BindingConversion
public static String doubletoString(double d) {
    return d == 0 ? "" : String.valueOf(d);
}
项目:DataBindingGuide    文件:CustomBindings.java   
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
项目:awesome-android-mvvm    文件:Converteractivity.java   
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    Log.d("BindingConversion","convertColorToDrawable:" + color);
    return new ColorDrawable(color);
}
项目:droidcon2016    文件:ViewBindings.java   
@BindingConversion
public static int convertBooleanToVisibility(boolean visible) {
    return visible ? View.VISIBLE : View.GONE;
}
项目:Architecture-Demo    文件:BindingHelpers.java   
@BindingConversion
public static int convertBoolToVisibility(boolean visible) {
    return visible ? View.VISIBLE : View.GONE;
}
项目:easydatabinding    文件:ObservableString.java   
@BindingConversion
public static String convertToString(ObservableString s) {
    return s.get();
}
项目:devfest-2016-realm    文件:Binding.java   
@BindingConversion
public static int booleanToVisibility(boolean visible) {
  return visible ? View.VISIBLE : View.GONE;
}
项目:NaikSoftware-Lib-Android    文件:Converters.java   
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
项目:NaikSoftware-Lib-Android    文件:Converters.java   
@BindingConversion
public static int convertBooleanToInt(Boolean value) {
    return (value != null && value) ? View.VISIBLE : View.GONE;
}
项目:binea_project_for_android    文件:DataBindingHelper.java   
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
项目:binding-collection-adapter    文件:BindingCollectionAdapters.java   
@BindingConversion
public static <T> Itembinding<T> toItembinding(OnItembind<T> onItembind) {
    return Itembinding.of(onItembind);
}
项目:MasteringAndroidDataBinding    文件:ConversionsActivity.java   
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
项目:TvDialog    文件:FontUtil.java   
@BindingConversion
    public static Typeface convertStringToFace(String fontName) {
        try {
            return Typeface.create(fontName,0);
        } catch (Exception e) {
            return null;
        }
    }

今天关于FZU - 2103 Bin & Jing in wonderland的介绍到此结束,谢谢您的阅读,有关10.Nodes and Bindings、Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制?、Android Data Binding 系列 (二) -- Binding 与 Observer 实现原理、android.databinding.BindingConversion的实例源码等更多相关知识的信息可以在本站进行查询。

本文标签:

上一篇PAT甲级——1118 Birds in Forest (并查集)(pat甲级1018)

下一篇OC+RAC(六) 核心方法bind(oracle rac核心技术详解)