缘起
在《关于Android学习的三个终极问题》一文的最后,我曾提到过在看完《信息简史》一书后,顿悟到“开发软件的时候仿佛能看到信息在流动”。《信息简史》是一本非Coding类的技术书籍,从它那里能感悟到什么?我曾经很小范围内交流过这方面的体会,今天可以借公众号分享给各位。
首先,我对软件开发这个工种有一个基本认识,即软件是人写得,人是活在现实世界里的。人们编写软件其实只是把自己的想法转换成代码,继而交给计算机来执行而已。而人类的想法是不能脱离我们对世界的认识。很简单的一个例子,猪、狗等动物的想法肯定和人不一样。
所以,结合本篇的标题“从信息传递的角度来看Android中的广播和Binder”,大家在阅读本文前要时刻提醒自己:
信息传递的角度:我们会先表明现实世界中的场景。
Android中的广播和Binder:是讲现实世界中的场景如何映射到Android开发中。
我一直说“基于Android,高于Android”,“基于Android”要求我们了解,摸清楚“术”,“高于Android”则是提醒大家注意具体“术”背后的“理”。只有真切掌握了“理”,未来再出现什么OS,大家都不用担心了——小样,别以为你穿上马甲我就不认识你了。
整体视频
由PPT转换而来的视频
上面视频里介绍了8个部分,分别是:
信息传递——古已有之
信息传递——生活中的场景
信息传递场景A——广播
Android中广播的实现
信息传递场景B——过程调用
远程过程调用的三个问题
Android中的Binder
回归信息传递的本质——一个简单的Binder框架commonRPC
每个部分都不长。大家可以先看下面的内容,等整体看完后,再来看上面的PPT视频。
信息传递——古已有之
上面这个图来自一个视频,如下:
这个视频说明,很早很早以前,人们就知道通过鼓声来传达信息。从某个意义上说,鼓声信息的传播和加工者就是那个时代的编码工作者了吧。
信息传递——生活中的场景
现在来看看生活中的两个场景。
这个图使用了前些年很火的一个案例,我做了一些拓展:
原文是“贾君鹏,你妈妈喊你回家吃饭”。这个case就是广播,发送者是否认识贾君鹏?不care。贾君鹏能不能收到这条消息?也不care。
拓展case则变成“@贾君鹏,你妈妈喊你回家吃饭”。在贾君鹏名字前多了一个@符号,以微博来说,这就是直接给贾君鹏发信息了。我们姑且将这个case称为过程调用。
先来看信息传递的第一种场景。
信息传递场景A——广播
看下图:
上图是生活中广播的场景映射到Android平台上的情况。简单来看,广播包括两个基本特点:
发送者只管发消息,不care谁接收。
接收者只关注自己感兴趣的消息。所以,在贾君鹏回家吃饭的案例中,和贾君鹏认识,或者可能知道他的人才会对那个消息有所动作。
上面这个信息传递的案例映射Android世界中会产生什么就很清楚了:
首先要提供广播发送的功能,即Context sendBroadcast系列API。
然后要提供接收广播的功能,其中最重要的就是把对哪些广播感兴趣的这个信息表达清楚。
Android中广播的实现
现在来具体看看Android中广播的实现,我将它分为基础功能和高级玩法两个部分。先看基础功能:
上图展示了Android中为了实现广播所提供的基础API或机制。嗯,从生活场景到Android的实现,映射起来就是如丝般顺滑。
当然,人的欲望是不会满足的,在基础功能上,Android还提供了一些高级功能。讲真,现实生活中的广播场景也有这么些高级功能,不过大家不怎么在意而已。
上图,Android中的广播机制还有几种高级玩法:
A 群发广播,无优先级之分。注意,这条其实是基本玩法了。作为对比,我单独列出来。
B 群发广播,但是有优先级之分,高优先级先接收广播,还能中断广播的后续发送。这个场景也很常见,比如很多重大消息都是先到高层,然后再传播到中低层,或者不传给中低层。
C 广播发了,但后来的接收者没收到。接收者说我就是得知道这个信息。怎么办?先想想现实生活中的解决办法?嗯,一定是有个地方存在,然后等你来的时候给你。所以,Android也是这么处理的。
以上是Android中的广播,现在我们来一个反向思考。
从信息传递的角度看,是不是大家(假设你是懂事的成年人)早就知道,体会,用过广播了?既然如此,到了Android,广播还有那么难吗?那些玩法,和现实生活中有什么不同?
再者,除了Android Broadcast外,UDP组播、D-BUS、UEvent是不是也是可以用作广播?
最后,仔细观察广播的参与方——接收者和发送者,是不是就是Publisher/Subscriber模式?嗯,果然就是换了一个马甲。
上面举得广播的例子,相信大家有一点理解本文的目的了。对,我并不是要教大家Android中广播的具体技术(这个问题我早在7、8年前《深入理解Android卷2》就源码分析过了)。而是想告诉大家,以后换一个平台,或者换一个什么新的技术,只要人类还存在,就一定会有广播的,而且,广播也一定是上面列得那些功能。
信息传递场景B——过程调用
接着来看信息传递场景之二——过程调用。
上面这个图中,我把“@贾君鹏,你妈妈喊你回家吃饭”转换为一个函数。目的是告诉贾君鹏一个消息,这个动作可以转换成图中的sendMessage函数,其中:
mrJiaJunPeng:标识who,即消息的接收者。
tell:是我对贾君鹏发出的动作。大家不要忽略这个tell。因为不是所有人都能“tell”,比如,聋哑人朋友就是打手势,而不能说话。
具体的消息:你妈妈喊你回家吃饭
其中,最后两条——tell和具体的消息再拔高抽象一下,两者对mrJiaJunPeng来说可组合起来当做一个信息(现在是两个,一个是动作,一个是具体的信息)。
上面讲这么多有什么用呢?接着看
简单来看,过程调用有两种形式:
本地过程调用,也就是Caller(调用者)和目标(mrJiaJunPeng)在一个进程里。这其实就是函数调用。90%都是这样的。
远程过程调用:也就是Caller和目标不在同一个进程里。怎么办?
上面是一个比较实际的代码片段了。技术上的问题是如何实现远程过程调用。而从信息传递的角度看,其实只有下面三个问题:
发给谁?
发什么内容?
怎么发?
下面我们来解决这三个问题。
远程过程调用的三个问题
我们先讨论“怎么发”的问题。这也是最能揭开诸多“马甲”的地方。来看下图:
图中列举了好几种“怎么发”的技术解决方法:
socket、pipe、shared memory(共享内存也可以作为信息传递的手段)。以上这几种方法在Android系统里都有用到,比如触屏事件的派发就是使用pipe。
还有更常见的技术方案,比如http、webservice等。
现在再来看Binder,你还觉得它有什么神奇之处吗?
无非就是交互双方都打开Kernel中的一个binder设备——这一点其实和socket,pipe很像。
通过binder,交互双方就可以收发信息了。当然,还有其它问题要解决。
接着看:
上图介绍了“发给谁”和“发什么内容”的解决办法:
发给谁:socket/http/webservice是通过地址+端口号/url来标识目标的。而pipe、shared memory通过fd来标识目标。
发什么内容:两种办法,一种是像binder这样使用自定义的协议(协议中会包含交互的双方是谁,信息以什么格式封装等)。还有一种就是基于诸如http这样的标准协议之上来处理。
好了,Binder的本质已经揭露了,它和任何之前、现在已知的其它RPC方法都没有本质区别。不过,为何大家会觉得Binder难呢?
Android中的Binder
我们为什么会觉得Binder难呢?说实话,Binder确实难。不过,为什么会这样?来看下图:
Binder难的原因有很多,代码复杂是一方面。但我觉得最难的地方(这种难就是让大家常常把Binder和其它RPC方法区分出来大书特书的关键原因)在于Binder把业务和通信混到一起了。
回顾非Binder的RPC我们会发现RPC其实包含两个部分:
先要建立通信连接,即先把通信层建好。
然后才是组织业务数据/协议,交互双方做发送和接收的动作。
而Binder通过一种巧妙的方式把A和B统一了——其目的是为了让你达到和调用本地函数一样的用户体验。所以:
之前认为本地函数调用都是LPC的你,突然一下子看到一个函数调用居然可以是RPC,瞬间就膜拜了。
好多改bug的同志,从日志里找不到Binder调用执行到哪了....
那么,Binder是怎么把普普通通的RPC搞得如此高大上呢?接着看:
上图中:
Binder其实存在一个通讯层,只不过封装得比较好,你一般看不到罢了。信息还是先通过binder设备传到通讯层的onTransact函数。
在onTransact函数中,信息中包含了业务函数的code。根据这个code,找到对于的业务函数,调用它。
通过这种方式,通讯+业务完美结合。
Binder好用吗?说实话我觉得有点麻烦。接着来看。
为了完美得使用Binder,在Java层中我们要干好几个事情:
为业务函数编写AIDL文件。AIDL文件描述的是业务功能函数。
如果交互双方要传递一些复杂参数,还要单独写一个Parcelable类以及单独写一个AIDL文件。
Binder这么做倒也无可厚非,只不过后来我做了好几个APP,每一个都要这么弄我就有点烦了。为何不能像前后台那样,把参数准备好,发送接收出去就完了?干嘛还得写AIDL定义一堆业务函数,还要为复杂参数再写一堆文件。
所以,我们来看一个基于Binder的简单的RPC框架。
回归信息传递的本质—一个简单的Binder框架commonRPC
下面是我们开发的基于Binder的commonRPC框架,很简单,就是不想写那么多AIDL。来认识下它:
图中,我们的解决思路就是:
commonRPC回归到通讯,不涉及业务。
作为通讯框架,它就提供call和notify两个函数。至于交互双方要发什么信息,完全由这两个函数的参数决定。
你可以把commonRPC看成HttpClient。当然,我们比HttpClient看起来高级一点,你还是能像本地函数一样调用。来看下图:
上图是使用commonRPC的客户端:
mRpc是commonRPC的客户端,调用callAsyncWithName就是调用远端的一个函数。
callAsyncWithName参数中,name是远端函数名,info是你要传给它的参数。一般而言,建议将参数json化。
当然,对于一些特殊的参数,比如Bitmap等,commonRPC也支持,不需要将Bitmap json化,它会利用binder本身的机制支持对大数据量参数的传输。
再来看看comonRPC的服务端:
cmomonRPC的服务端和Binder的通讯层有些类似:
onCall是服务端收到客户端请求的入口。
onCall里,服务端解析参数,然后再去调用对应的业务函数。
说实话,commonRPC服务端其实是把Binder对大家隐藏在通讯层里干得事情又重新暴露在开发者眼前了。嗯,有时候封装太完美也让我们丧失了一些自由。
就我的情况来说,我们好些个APP的架构都差不多:
用户操作,给隐藏在后面的Service发请求。
Service再把请求HTTP发给后台。
后台处理请求,结果返回给Service,从而转给用户UI。
借助commonRPC,我们就可以打通UI到Service到后台的统一协议。例如,UI给Service发的请求和Service给后台发的请求一样——可能略有区别,但也不过是一些包装而已,核心的信息一定是一样的——毕竟,从UI到最终的后台,我们都是在传递信息,参与方是务必要保证信息的准确和一致的。
到此,我们对信息传递的第二个场景以及Binder也介绍完了。现在我们来看看最终的结论。
为什么我觉得少儿学习编程完全没必要?
IT,全称是Information Technology,信息科技。码农是属于IT行业里的,但我不知道有多少码农真切感受到自己在操作“信息”了。
我对编程的定义很广泛和open——凡是一切能让其它人、机器按照自己想法来工作的,我都认为是coding:
先看程序员,我们把自己的想法以代码的方式编写出来,然后交给机器去执行。这是通常意义上的coding。在这个场景中,想法就是信息,信息从你的大脑中以代码的方式传达给了编译器,再由编译器转换成了CPU识别的东西来执行。
有了上面的场景,请问教师是不是在编程?教师把如何做人做事的信息传递给了学生,学生从此就有了“文明的行为”,这难道不是编程?为什么家长想方设法要把孩子放到优良教育环境下?因为在一个恶劣环境下,孩子是可能被教——也就是“coding”坏的。
再看最后一个例子,工作中,我们领导经常有一些想法和下属交流,希望下属最终实现这些想法。这是不是领导在编程呢?
回归到信息上,上面的场景都是在传达,加工,处理信息。基于这种认识,为何还要让几岁的小朋友去浪费时间学习具体的编程之术?掌握广义的Coding才是最重要的。
另外,老话说,做人做事做学问。做学问是放在最后的,对小朋友而言,掌握如何做人和做事不比从小就拘泥于编程的具体套路中能让他们走得更远,更好?
最后的最后
我期望的结果不是朋友们从我的书、文章、博客后学会了什么知识,干成了什么,而应该是说,神农,我可是踩在你的肩膀上得喔。
关于学习方面的问题,我已经讨论完了。后面这个公众号将对一些基础的技术,新技术做一些学习和分享。也欢迎你的投稿。不过,正如我在公众号“联系方式”里说的那样——郑渊洁在童话大王《智齿》里有一句话令我印象深刻,大意是“我有权保持沉默,但你说的每一句话都可能成为我灵感的源泉”。所以,影响不是单向的,很可能我从你那学到的东西更多。
神农和朋友们的杂文集
长按识别二维码关注我们