Binder牌胶水,在Android中无处不在

pzxe5248 8年前
   <p><img src="https://simg.open-open.com/show/43e8fd6a7bf78a38b2f90bd4580d8027.jpg" alt="Binder牌胶水,在Android中无处不在" width="1024" height="512"></p>    <p><br> Binder牌胶水,如雷贯耳,在Android中无处不在,是每个Android程序猿居家旅行必备。有了它的存在,我们甚至可以不用深入了解App进程和系统进程、用户空间和内核空间、跨进程通讯等概念也可以做好应用层开发。但是呢,阅读本文之前,还是建议先看看<a href="http://www.open-open.com/lib/view/open1452649194089.html">《Binder学习指南》</a>了解下这些概念。</p>    <p>Binder属于“<a href="/misc/goto?guid=4959672158714340849">问题领域</a>”的命名?见名知意,作为胶水,“粘合”是他的主要职责。在Androd世界里,他到底粘合了什么?比如我们经常使用的<code>startActivity()</code>就涉及到进程通讯,Binder粘合了这些进程,弱化了我们对进程的感知,降低了开发难度。</p>    <p>本文来聊聊四瓶Binder胶水,他们功效不同,配方却完全一致。此文的重点有两个,<strong>其一,介绍下他们的配方,也就是类图模型</strong>,了解他们,对我们阅读Android源码,寻找源码的位置有很大的帮助;<strong>其二,既然作为胶水,我们要研究下他粘合了什么东西,粘合了哪些进程</strong>。这四瓶胶水的功效和配方分别是:</p>    <ol>     <li>AIDL:<br> <code>IInterface--Stub--Proxy--Stub具体实现</code></li>     <li>ContentProvider:<br> <code>IContentProvider--ContentProviderNative--ContentProviderProxy--ContentProvider.Transport</code></li>     <li>管理四大组件的AMS:<br> <code>IActivityManager--ActivityManagerNative--ActivityManagerProxy--ActivityManagerService</code></li>     <li>负责ActivityThread和AMS之间的通讯<br> <code>IApplicationThread--ApplicationThreadNative--ApplicationThreadProxy--ApplicationThread</code></li>    </ol>    <p>Binder文章很多,很难写出彩,相比于一言不合就晒C++代码,晒cpp文件的,本文重点还是从Java层出发解释Binder机制。本文所有代码在此:<br> <a href="/misc/goto?guid=4959673759229761095">https://github.com/geniusmart/binder-project</a></p>    <h2>Binder胶水的原始配方</h2>    <p><img src="https://simg.open-open.com/show/f2ffd0708d65dc3d59008706be40786b.png" alt="Binder牌胶水,在Android中无处不在" width="497" height="249"></p>    <p>如上图,IInterface/IBinder/Binder/BinderProxy是Binder机制的核心api,理解这些接口/类,是研究Binder的前提。</p>    <p>接口通常代表所具备的能力,比如我们熟悉的Api里,<code>Serializable</code>和<code>Parcelable</code>代表其实现类是可序列化的;<code>Iterable</code>代表可迭代遍历,因此其实现类<code>HashSet</code>和<code>ArrayList</code>可使用<code>Iterator</code>进行遍历。</p>    <p>在这个类图的最顶层,有两个接口,<code>IInterface</code>和<code>IBinder</code>。<code>IBinder</code>代表<strong>跨进程传输的能力</strong>,而<code>IInterface</code>则代表<strong>远程服务端具备的能力</strong>。</p>    <p><code>Binder</code>是<code>IBinder</code>的实现类,因此它具备跨进程传输的能力,它实际上就是<strong>远程Server端的Binder对象本身</strong>。</p>    <p><code>Binder</code>对象是给Server端对象本身,是Server进程用的,与此对应的<strong><code>BinderProxy</code>则是远程<code>Binder</code>的代理对象</strong>,给Client进程用的(源码位于<code>Binder</code>类内部)。在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。</p>    <p>以上都是冷冰冰的理论,读到这里我们仍然很困惑,我们需要能亲手掌控两个进程:Client进程和Server进程,并进行通讯,以此来熟悉Binder机制。因此,第一瓶胶水应运而生——AIDL,AIDL能实现这个需求。</p>    <blockquote>     <p>以上四个类是<code>android.os</code>包给我们提供的Binder机制相关的api,基于这四个类,我们可以扩展出各种各样的Binder模型,实现各种各样的跨进程传输的场景。而本文所描述的四瓶胶水便是基于此套api的四种实现。</p>    </blockquote>    <h2>第一瓶胶水AIDL</h2>    <p>假设你已经熟练掌握了AIDL,首先写个<code>ICompute.aidl</code>,代码很简单,如下:</p>    <pre>  <code>// ICompute.aidl  package com.geniusmart.binder.aidl;    interface ICompute {      int add(int a,int b);  }</code></pre>    <p>此时,编译器会帮我们生成一个ICompute.java文件,这个类的可读性很差,为方便大家查阅,我将代码做了格式化,并拷贝一份放在temp包下,<a href="/misc/goto?guid=4959673759313018327">点击这里查看</a>。</p>    <p>这个类的细节我不打算多讲,大家可以查看一下文章开篇的那篇文章。对照着生成出来的ICompute.java文件,绘制如下Binder模型图:</p>    <p><img src="https://simg.open-open.com/show/7ed3772c7b9033a8e92d7a28d958b056.png" alt="Binder牌胶水,在Android中无处不在" width="523" height="572"></p>    <p>AIDL的Binder模型</p>    <p>1. 熟悉的配方:ICompute-Proxy-Stub-Stub具体实现</p>    <p>通过上图,我们发现AIDL的Binder模型是基于原始配方的扩展。当我们写完ICompute.aidl之后,<code>ICompute</code>,<code>Stub</code>,<code>Proxy</code>已经自动生成出来,他们的作用如下:</p>    <ul>     <li><code>ICompute</code>接口继承了<code>IInterface</code>,并写了<code>add(a,b)</code>的方法,代表Server端进程具备计算两数相加的能力。此方法将在<code>Stub的具体实现</code>类中重写。</li>     <li><code>Stub</code>是一个抽象类,他本质是一个<code>Binder</code>,他存在的目的之一是约定好<code>asInterface</code>,<code>asBinder</code>,<code>onTransact</code>方法,减少我们开发具体<code>Binder</code>对象的工作量。此外,<code>Stub</code>即需要<strong>跨进程传输</strong>,又需要<strong>约定远端服务端具备的能力</strong>,因此他需要同时实现<code>IInterface</code>和<code>IBinder</code>接口,通过上文的类图可以看得出来。</li>     <li><code>Proxy</code>是代理对象,它在Client进程中使用,作用是以假乱真。他持有<code>BinderProxy</code>对象,<code>BinderProxy</code>能帮我们访问服务端Binder对象(本例中即<code>Stub具体实现类</code>)的能力。</li>    </ul>    <p>AIDL让我们可以饭来张口衣来伸手,但是,服务端具备的具体能力,AIDL是没法给的,需要我们自力更生,所以接下来我们来写个<code>Stub的具体实现</code>,并验证下这个Binder模型的准确性。</p>    <p>2.验证远程对象和代理对象</p>    <p>在Server进程启动一个Service,定义Stub的实现类<code>ComputeBinder</code>,等待Client进程的连接,代码如下:</p>    <pre>  <code>public class ComputeService extends Service {        public static final String TAG = "Server进程";        @Override      public IBinder onBind(Intent intent) {          return new ComputeBinder();      }        private static class ComputeBinder extends ICompute.Stub {            @Override          public int add(int a, int b) throws RemoteException {              Log.i(TAG, TAG + this.getClass().getName() + "执行add()");              return a + b;          }      }  }</code></pre>    <p>接下来,我们来写连接远程服务的代码 ,核心代码如下:<a href="/misc/goto?guid=4959673759392928404">(完整代码点击查看)</a></p>    <pre>  <code>private ServiceConnection conn = new ServiceConnection() {      @Override      public void onServiceConnected(ComponentName name, IBinder service) {          Log.i(TAG, TAG + "触发onServiceConnected : " + service.getClass().getName());          mICompute = ICompute.Stub.asInterface(service);          Log.i(TAG, TAG + "触发asInterface : " + mICompute.getClass().getName());          Log.i(TAG, TAG + "触发add() : 远程方法执行结果为" + mICompute.add(3, 5));      }  };</code></pre>    <p>这里我设计了两种场景,分别让Server进程和Client进程执行连接远程服务,输出结果如下:</p>    <ul>     <li>Client进程绑定Server进程的Service获取Binder<br> (1)Client进程输出日志: <pre>  <code>Client进程触发onServiceConnected : android.os.BinderProxy  Client进程触发asInterface : ICompute$Stub$Proxy  Client进程触发add() : result = 8</code></pre> (2)Server进程输出日志: <pre>  <code>Server进程ComputeService$ComputeBinder执行add()</code></pre> </li>     <li>Server进程绑定自身进程内的Service获取Binder<br> Server进程输出日志: <pre>  <code>Server进程触发onServiceConnected : ComputeService$ComputeBinder  Server进程触发asInterface : ComputeService$ComputeBinder  Server进程ComputeService$ComputeBinder执行add()  Server进程触发add() : 远程方法执行结果为8</code></pre> </li>    </ul>    <p>结论:</p>    <ul>     <li>当Client进程连接远程时,会经过如下步骤:客户端获得<code>BinderProxy</code>对象->转换为<code>ICompute$Stub$Proxy</code>->服务端<code>ComputeService$ComputeBinder</code>执行相应的方法,并将数据返回给客户端->客户端取得数据。</li>     <li>当Server进程连接自身时,始终调用的是Binder本体对象<code>ComputeService$ComputeBinder</code></li>    </ul>    <p>3.工作原理</p>    <p>说好了不谈细节,但是对于讲清楚Binder机制来说,不谈细节臣妾真的做不到,受限于篇幅,我还是克制住了,取而代之的是一张基于aidl的工作流程图,与其贴代码讲细节,还不如大家对照源码和流程图自己解析aidl的工作原理。如下:</p>    <p><img src="https://simg.open-open.com/show/b79965b174782fadfca004b4206895cf.jpg" alt="Binder牌胶水,在Android中无处不在" width="1025" height="934"></p>    <p>4. 这瓶胶水粘合了什么?</p>    <p>这个问题对于AIDL来说很简单,他粘合了需要通讯的两个进程,在上文我们称之为Server进程和Client进程,在上文的UML类图中也有所体现。</p>    <h2>第二瓶胶水ContentProvider</h2>    <p>为了巩固这个模型,Binder牌胶水隆重推出第二个款式ContentProvider。谈起跨进程,我们自然而然会想起内容提供者。即使你不了解ContentProvider的原理,我们平常在使用api的时候,所做的事情就是让<strong>内容使用者进程</strong>去访问<strong>内容提供者进程</strong>的数据,所以这第二瓶胶水很贴近我们日常的Android开发生活。</p>    <p>首先先给出结论,也就是有关ContentProvider的Binder模型图:</p>    <p><img src="https://simg.open-open.com/show/91a5c399b88ff59bf009bbec53e2b987.jpg" alt="Binder牌胶水,在Android中无处不在" width="716" height="882"></p>    <p>1. ContentProvider Binder模型来源</p>    <p>接下来我们来分析下这张图是怎么来的:<br> 作为一名Coder,应该永远保持好奇心,使用<code>ContentProvider</code>时,我们经常用这样的api:</p>    <p><code>getContentResolver().query()/insert()/update()/delete()</code>,你只要点进去看看源码,都能很轻易发现这个模型图,这个过程大概是这样的:</p>    <ul>     <li>(1) 从内容使用者的角度出发,查看<code>insert()</code>源码</li>     <li>(2) 发现关键代码<code>IContentProvider provider = acquireProvider(url);</code></li>     <li>(3) 活捉一只<code>IInterface</code>的子接口<code>IContentProvider</code>,对应AIDL的<code>ICompute</code></li>     <li>(4) 那么问题来了,对应的<code>Proxy\Stub\Stub具体实现</code>在哪?一筹莫展中。</li>     <li>(5) 此路不通,换一条路,即从内容提供者角度出发,查看 <code>ContentProvider</code>的源码,找找线索。</li>     <li>(6) AS切换到Structure视图,观察类的结构,此时应该有点运气成分,我们发现了内部类<code>Transport</code>,他的继承关系是:<code>class Transport extends ContentProviderNative{}</code>。虽说此步需要点运气,但是<code>Transport</code>的中文意思为运输,颇有点跨进程传输的意思,所以也并非毫无根据。</li>     <li>(7) 看<code>ContentProviderNative</code>源码,发现其继承关系<code>class ContentProviderNative extends Binder implements IContentProvider{}</code>,这就是我们熟悉的模型啊,对应AIDL中的<code>Stub</code>,而<code>Transport</code>则对应着<code>Stub的</code>具体实现类<code>ComputeBinder</code>。</li>     <li>(8) 在<code>ContentProviderNative</code>内部,与他同一级的还有一个类,即<code>ContentProviderProxy</code>,至此,Binder模型相关的4个类或接口,我们已经集齐完毕。</li>     <li>(9) 最后,召唤神龙,深入学习ContentProvider的原理。</li>    </ul>    <p>2.验证远程对象和代理对象</p>    <p><code>ContentProvider</code>与AIDL不一样,AIDL中Binder对象可以轻易拿到,而我们在使用<code>ContentProvider</code>时候,通常都是通过<code>ContentResolver</code>来执行CRUD的操作,显然,他并不是一个Binder对象,追溯其源码,我们可以发现这样一行关键的代码:</p>    <pre>  <code>public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {        IContentProvider provider = acquireProvider(url);      //省略若干代码..  }</code></pre>    <p>看到返回类型<code>IContentProvider</code>时就是你大声呼喊“真相只有一个”的时候,阅读源码的乐趣就在于抽丝剥茧,轻解衣裳,窥探最本真和纯洁的胴体。。。</p>    <p>这时候问题又来了,<code>acquireProvider(url)</code>是一个<code>@hide</code>方法,没法直接调用。解决办法很简单,即使用反射。核心代码如下:</p>    <pre>  <code>Uri uri = Uri.parse("content://com.geniusmart.binder.AccountProvider/account");  ContentResolver contentResolver = getContentResolver();  //通过反射调用hide方法  Method method = ContentResolver.class.getMethod("acquireProvider", new Class[]{Uri.class});  Object object = method.invoke(contentResolver, uri);  //打印Binder类型  Log.i(TAG, object.getClass().toString());</code></pre>    <p>与AIDL中的验证思路一样,我们来分别验证下客户端和服务端执行此段逻辑之后的结果:</p>    <ul>     <li>Client进程使用Server进程的ContentProvider<br> 输出结果为:<code>class android.content.ContentProviderProxy</code></li>     <li>Server进程使用自己的ContentProvider<br> 输出结果为:<code>class android.content.ContentProvider$Transport</code></li>    </ul>    <p>结论:Client进程通过<code>ContentProviderProxy</code>访问Server进程的<code>ContentProvider$Transport</code>,实现进程间通讯。</p>    <p>以上所有代码均在文章开篇的Github项目里。</p>    <p>3. 这瓶胶水粘合了什么?</p>    <p>这个问题也比较简单,ContentProvider这瓶胶水粘合了内容使用者和内容提供者两个进程,写<code>ContentProvider</code>的这一方称之为Server进程,用<code>ContentResolver</code>的这一方称之为Client进程,两个进程通过Binder进行通讯。</p>    <h2>另外两瓶胶水AMS和ApplicationThread</h2>    <p>AIDL和ContentProvider这两个Binder模型,都有清晰的Client进程和Server进程,理解起来相对容易,而另外两个Binder模型所涉及到的进程双方则比较模糊。AMS和ApplicationThread有着千丝万缕的关系,所以我们放在一起讲。首先贴一下这两瓶胶水的配方:</p>    <p><img src="https://simg.open-open.com/show/2dd21f5c92fcdb149880a048c0f1c6fa.jpg" alt="Binder牌胶水,在Android中无处不在" width="768" height="651"></p>    <p>AMS的Binder模型</p>    <p><img src="https://simg.open-open.com/show/057615cfd81064bb8ac7e3fae16df5e3.jpg" alt="Binder牌胶水,在Android中无处不在" width="705" height="613"></p>    <p>ApplicationThread的Binder模型</p>    <p>看到这两个Binder模型图,真是感慨,这简直是一样的配方,熟悉的味道,所以以后如果有看到类似<code>IXxxx/XxxxProxy/XxxxNative/Xxxx</code>的命名规则,一定要想起这是Binder的配方。</p>    <p>Binder模型来源</p>    <p>这两个Binder模型图从何而来?<br> 在一个夜黑风高的晚上,你我寂寞难耐,准备Fuck Source Code,于是决定挑个并不软的柿子来捏一捏,他就是<code>startActivity()</code>,大概会这么一些个前戏:<br> (1)<code>context.startActivity()</code><br> (2)<code>mBase.startActivity()</code><br> (3)<code>ContextImpl.startActivity()</code><br> (4)</p>    <p><code>mMainThread.getInstrumentation().execStartActivity()</code><br> (5)<code>Instrumentation.execStartActivity()</code></p>    <p>源码阅到这里已经有一个小高潮了,因为我们发现了如下代码:</p>    <pre>  <code>//IApplicationThread 是IInterface类型  IApplicationThread whoThread = (IApplicationThread) contextThread;  //ActivityManagerNative是Binder和IInterface类型  ActivityManagerNative.getDefault().startActivityAsUser()</code></pre>    <p>此时Binder机制的雏形我们已经心中有数,之后少侠们可以各自发挥,深入理解这两个Binder机制的作用,获得更多的快感。</p>    <p>所以,理解这个模型的意义在于,当我们看到XxxxProxy执行逻辑时,当前进程所属的角色是Client进程,要查看该逻辑源码的时候,应该找到Server进程的Xxxx类查看。</p>    <p>Activity的启动流程的解析漫长而枯燥,不是此文的重点,大家了解下这两个类的意义即可:</p>    <ul>     <li><code>ActivityManagerService</code><br> AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作。</li>     <li><code>ApplicationThread</code><br> <code>ApplicationThread</code>是<code>ActivityThread</code>的内部类,负责<code>ActivityThread</code>和<code>ActivityManagerService</code>之间的通讯。</li>    </ul>    <p>这两瓶胶水粘合了哪些进程?</p>    <p>通过Activity启动流程图我们来看下这两个Binder模型是如何发挥作用的,如下图:</p>    <p><img src="https://simg.open-open.com/show/66187dc755383e5869bc1308d203e3ac.jpg" alt="Binder牌胶水,在Android中无处不在" width="960" height="720"></p>    <p>Activity启动流程图</p>    <p><em>注:此张图片来源于这篇文章<a href="/misc/goto?guid=4959673759475425891">《startActivity流程分析(一)》</a>,已征得博主同意引用在此处。</em></p>    <p>在这张图里,我们意识到除了第一、二瓶胶水描述到的App进程之外,Android世界里还有Launcher进程,系统进程等等,而Binder在其中如鱼得水,散发着万丈光芒。</p>    <p><code>AMS</code>位于系统进程,处于Server进程的位置,Launcher进程和App进程作为Client进程,持有<code>ActivityManagerProxy</code>,与AMS进行通讯,召唤四大组件。而<code>ApplicationThread</code>位于应用进程,处于Server进程的位置,系统进程则作为Client进程,持有<code>ApplicationThreadProxy</code>,使得应用进程中的主线程(<code>ActivityThread</code>)和AMS之间可以进行通讯。</p>    <p>至此,对于Binder机制的认知应该要有所升华。</p>    <h2>总结</h2>    <p>用一张表格描述一下上文所讲的四个Binder模型:</p>    <table>     <thead>      <tr>       <th>能力</th>       <th>IInterface</th>       <th>Binder抽象</th>       <th>BinderProxy</th>       <th>Binder</th>      </tr>     </thead>     <tbody>      <tr>       <td>AIDL</td>       <td><code>ICompute</code></td>       <td><code>Stub</code></td>       <td><code>Proxy</code></td>       <td><code>ComputeBinder</code></td>      </tr>      <tr>       <td>内容提供者</td>       <td><code>IContentProvider</code></td>       <td><code>ContentProviderNative</code></td>       <td><code>ContentProviderProxy</code></td>       <td><code>ContentProvider.Transport</code></td>      </tr>      <tr>       <td>AMS</td>       <td><code>IActivityManager</code></td>       <td><code>ActivityManagerNative</code></td>       <td><code>ActivityManagerProxy</code></td>       <td><code>ActivityManagerService</code></td>      </tr>      <tr>       <td>ActivityThread和AMS之间的通讯</td>       <td><code>IApplicationThread</code></td>       <td><code>ApplicationThreadNative</code></td>       <td><code>ApplicationThreadProxy</code></td>       <td><code>ApplicationThread</code></td>      </tr>     </tbody>    </table>    <p>Binder并不神秘,对于应用层来说,Binder便是上文中我所绘制的各种模型图,我们需要明确两点,<strong>第一,Proxy作为代理对象,以假乱真,Client进程通过持有Proxy对象,进而调用Server进程中实际Binder对象的能力;第二,在使用任何一个Binder对象时,我们要明确,此时进行通讯的是哪两个进程,以及哪个进程充当Client或Server进程。</strong></p>    <p>最后多说一句,关于Binder写了那么多,大牛们自成体系自然无需阅读此小文,而小小牛们看完之后也许仍然十分懵懂,所以写Binder文章略显尴尬,而在这个过程中收获最大的其实是作者本人。学习Binder,没有捷径,找准切入点(本文的切入点在于应用层的Binder模型以及每种模型涉及通讯的两个进程),然后Read the Fucking Source Code,最后写写文章做总结。</p>    <h2>参考文章</h2>    <p><a href="http://www.open-open.com/lib/view/open1452649194089.html">Binder学习指南</a><br> <a href="/misc/goto?guid=4959673759475425891">startActivity流程分析(一)</a><br> <a href="/misc/goto?guid=4959673759571007292">Android应用程序组件Content Provider简要介绍和学习计划</a><br> <a href="/misc/goto?guid=4959654522940374715">Android进程间通信(IPC)机制Binder简要介绍和学习计划</a></p>    <p><br>  </p>    <p>文/<a href="/misc/goto?guid=4959673759684597290">geniusmart</a>(简书)<br>  </p>    <p> </p>