Binder进程间通信详解

前言

隔行如隔山,这句话同样适用于任何时候,即时同一个专业,深入下去的话,差别也是巨大的。今天,讲下自己如何学习binder进程间通信的机制的一些见解。开始的时候,只知道 Binder 是个很底层的东西,甚至对于具体是什么用都不是很清楚。

主要是通过两种方式:

  • 看别人写的Binder博文 目的很简单,大概了解Binder是个什么东西,有哪些核心的东西,对于看源码的时候选择性过滤有帮助,最好是看了后画下思维导图总结下或者可以画下流程图。
  • 看Binder源码 对于切入点的话,从最熟悉的客户端入手;选择典型的具体例子,分析下前面从他人那边看到的重点。

Binder世界之门

  • 学习如何使用东西和思考为什么要创造东西是完全不一样的,很多人写文章往往是忽略了后者。

想下如果两个进程需要相互通信,需要做什么? 假设其中一个进程是客户端进程,另一个是服务端进程,这里约定简称客户端与服务端。

  • 1.客户端需要知道哪一个是他要调用的服务端的方法。
  • 2.客户端如何传递和接收数据给服务端。
  • 3.屏蔽底层通信的细节,包括数据交换通过共享内存。

第一个问题很简单,搞一个唯一的标识符,通过包名+类名。 第二个问题,可以使用实现Parcelable接口的类,why? 这是因为 Android 系统可通过它将对象分解成可编组到各进程的原语。 第三个问题,封装一个类来具体的实现,他的名字叫Binder,然后这个binder的话,需要服务端来继承。

具体看源码

找好切入点

  • 由于binder是支撑Android系统的重要组成部分,binder从源码来说是很庞大的,所以这里找一个好的切入点变得非常重要。正所谓,前人栽树,后人乘凉,参考 彻底理解Android Binder通信架构 此文,切入点是startService开始的,具体分析的是客户端进程调用服务端进程的过程。

[=> ActivityManagerNative::ActivityManagerProxy]

public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, int userId) throws RemoteException
    {
        //这一步操作主要实例了两个Parcel对象,datat是客户端的发送数据,reply是服务端返回的数据 
        //具体参考:Parcel的obtain和recycle方法
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
        service.writeToParcel(data, 0);
        data.writeString(resolvedType);
        data.writeInt(userId);
        //
        mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
        reply.readException();
        ComponentName res = ComponentName.readFromParcel(reply);
        //具体参考:Parcel的obtain和recycle方法
        data.recycle();
        reply.recycle();
        return res;
    }
Parcel的obtain和recycle方法
  • 这两个方法看名字很熟悉有没有?前次讲的handler中生成的Message其实也有这两个方法,知觉告诉我,这两处地方的原理是一样的,心动不如行动,马上进入验证环节。
obtain方法

[=> Parcel.java::obtain]

    private static final int POOL_SIZE = 6;
    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
    ...
    ...
    ...
  /**
     *说明下:这个pool是上面声明的一个6容量的Parcel数组,方法中省略若干代码
     *从这个pool中检索一个新的 Parcel 对象
     */
    public static Parcel obtain() {
        final Parcel[] pool = sOwnedPool;
        synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool[i];
                if (p != null) {
                    pool[i] = null;
                    return p;
                }
            }
        }
        //这里参数传的是0
        return new Parcel(0);
    }

    ...
    ...
    ...
    private Parcel(int nativePtr) {
        init(nativePtr);
    }

    private void init(int nativePtr) {
        if (nativePtr != 0) {
            mNativePtr = nativePtr;
            mOwnsNativeParcelObject = false;
        } else {
            //nativeCreate是个native方法,参考:nativeCreate方法,mNativePtr这个是可以理解成指针
            mNativePtr = nativeCreate();
            mOwnsNativeParcelObject = true;
        }
    }
    
    private void freeBuffer() {
        if (mOwnsNativeParcelObject) {
            nativeFreeBuffer(mNativePtr);
        }
    }
  • obtain方法:如果缓存Parcel数组不为空则使用缓存数组,否则自家创建一个Parcel。
nativeCreate方法

[=> android_os_Parcel.cpp::android_os_Parcel_create]

/**
*这里来看的话,很清楚的看到返回的是parcel的指针
**/
static jint android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    Parcel* parcel = new Parcel();
    return reinterpret_cast<jint>(parcel);
}
recycle方法

[=> Parcel.java::recycle]

 /**
     * 把一个Parcel对象放回到pool池中。在这回调之前,不需要这个对象。You must not touch
     * the object after this call.
     */
    public final void recycle() {
        //这个方法内部调的是native方法,其实是根据指针释放内存
        freeBuffer();

        final Parcel[] pool;
        //这里其实是在obtain无参方法中实例化创建过程中赋值为true
        if (mOwnsNativeParcelObject) {
            pool = sOwnedPool;
        } else {
            //mNativePtr可以看做是指向Parcel对象的指针,sHolderPool也是一个6容量的Parcel数组 
            mNativePtr = 0;
            pool = sHolderPool;
        }
        //放到缓存数组中
        synchronized (pool) {
            for (int i=0; i<POOL_SIZE; i++) {
                if (pool[i] == null) {
                    pool[i] = this;
                    return;
                }
            }
        }
    }
   ...
   ...
   ...
    
    private void freeBuffer() {
        if (mOwnsNativeParcelObject) {
            nativeFreeBuffer(mNativePtr);
        }
    }
  • recycle方法:根据mOwnsNativeParcelObject的值,若为true,则不带参数的obtain方法获取的对象,把其放入sOwnedPool中,否则带nativePtr的obtain(int obj)方法获取nativePtr指向的对象,把其放入sHolderPool中。

看了源码后发现,其实这个原理类似的,缓存的方式有区别,Message是通过链表方式来进行,Parcel是通过固定的数组,异曲同工之妙。

探秘mRemote

涉及代码:需要解决两个问题,一个是mRemote哪里来,另一个是mRemote的transact方法

mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);

mRemote哪里来

  • 搜寻发现,ActivityManagerProxy 类的构造方法进行赋值,而 ActivityManagerProxy(AMP) 这个类又被 asInterface 方法调用。

[-> ActivityManagerNative.java::asInterface]

public abstract class ActivityManagerNative extends Binder implements IActivityManager {
    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        //此处obj = BinderProxy, descriptor = "android.app.IActivityManager";
        IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) { //此处为null
            return in;
        }
        return new ActivityManagerProxy(obj);
    }
    ...
}

此时obj为BinderProxy对象, 记录着远程进程system_server中AMS服务的binder线程的handle.

[-> ActivityManagerNative.java::ActivityManagerProxy]

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote){
        mRemote = remote;
    }
}

可知mRemote便是指向AMS服务的BinderProxy对象。

mRemote的transact方法

[-> Binder.java::BinderProxy]

final class BinderProxy implements IBinder {
    public native boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;

mRemote实际上调用了 BinderProxytransact 方法,而transact 调用了native方法mRemote.transact() 方法中,经过jni调用android_os_BinderProxy_transact方法。

android_os_BinderProxy_transact

[-> android_util_Binder.cpp]

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
    jint code, jobject dataObj, jobject replyObj, jint flags)
{
    ...
    //将java Parcel转为c++ Parcel
    Parcel* data = parcelForJavaObject(env, dataObj);
    Parcel* reply = parcelForJavaObject(env, replyObj);

    //gBinderProxyOffsets.mObject中保存的是new BpBinder(handle)对象
    IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject);
    ...

    //此处便是BpBinder::transact()
    status_t err = target->transact(code, *data, reply, flags);
    ...

    //最后根据transact执行具体情况,抛出相应的Exception
    signalExceptionForError(env, obj, err, true , data->dataSize());
    return JNI_FALSE;
}

gBinderProxyOffsets.mObject中保存的是BpBinder对象, 这是开机时Zygote调用AndroidRuntime::startReg方法来完成jni方法的注册.

其中register_android_os_Binder()过程就有一个初始并注册BinderProxy的操作,完成gBinderProxyOffsets的赋值过程. 接下来就进入该方法.

后续分析==

参考资料

简单明了,彻底地理解Binder

彻底理解Android Binder通信架构

一篇文章了解相见恨晚的 Android Binder 进程间通讯机制

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏walterlv - 吕毅的博客

Introducing MSTestEnhancer to make unit test result easy to read

发布于 2018-03-05 06:21 更新于 2018-08...

8810
来自专栏大大的微笑

java使用mina和websocket通信

这里以mina整合springMVC为例: //springMVC的配置: <!-- mina --> <bean class="org.spring...

1.6K100
来自专栏微服务生态

玩转Flume之核心架构深入解析

前段时间我们分享过玩转Flume+Kafka原来也就那点事儿和Flume-NG源码分析-整体结构及配置载入分析这二篇文章,主要介绍了flume的简单使用和配置文...

11130
来自专栏玩转JavaEE

Spring常用配置(二)

按:最近公众号文章主要是整理一些老文章,主要是个人CSDN上的博客,也会穿插一些新的技术点。 ---- OK,上篇博客我们介绍了Spring中一些常见的配置,上...

36230
来自专栏cloudskyme

jbpm5.1介绍(6)

Junit测试的mini流程helloworld 这是一个在demo中使用的Script Task做的简单示例,在执行到这个任务结点的时候自动输出"hello...

36770
来自专栏玩转JavaEE

Spring RestTemplate中几种常见的请求方式

在Spring Cloud中服务的发现与消费一文中,当我们从服务消费端去调用服务提供者的服务的时候,使用了一个很好用的对象,叫做RestTemplate,当时我...

1.1K60
来自专栏一个会写诗的程序员的博客

《Spring Boot极简教程》第9章 Spring Boot集成Scala混合Java开发参考资料

本章我们使用Spring Boot集成Scala混合Java开发一个Web性能测试平台。

21720
来自专栏生信宝典

Python学习教程(五)

作业(二) 将 “作业(一)” 中的程序块用函数的方式重写,并调用执行 def func(para1,para2,…): func(para1,para2,…)...

21490
来自专栏IT笔记

MongoDB从入门到“精通”之整合JavaWeb项目

好了,前两篇扯了这么多。开始的开始,我们也无须了解的更加深入。重点是要整合到项目中去,在实践中发现问题,追踪问题,然后解决问题。 ? 3004.jpg 准备 M...

68150
来自专栏逆向技术

COM_第四讲_保存GUID_优化使用代码

    优化以前的代码,让使用者更方便 一丶 优化思路 1.我们可以将我们写的GUID(类工厂的ID)保存到注册表中,并且保存一下DLL的文件路径,遍历注册表去...

23400

扫码关注云+社区

领取腾讯云代金券