美文网首页Android开发Android开发经验谈程序员
IPC -通过AIDL看Binder的进程间通信过程

IPC -通过AIDL看Binder的进程间通信过程

作者: 千山万水迷了鹿 | 来源:发表于2017-09-13 17:00 被阅读101次

    AIDL是 Android Interface definition language的缩写,即:Android接口定义语言。

    进程隔离:不同进程间不可以相互访问内存空间,要想相互调用,必须要进行进程间通信。

    本篇中不涉及Binder的底层原来,但是要理解一个知识点:客户端进程持有BinderProxy类的对象,通过Binder驱动,向对应的运行在服务端进程中的Binder对象发送消息(执行远程方法调用)。可以类比java的Socket编程中的,Socket 向SocketServer发送消息的过程,不过Binder不仅仅是发送报文消息那么简单,他对远程方法调用实现了封装。

    注:这不是一篇介绍如何使用AIDL的文章(如果想学习aidl如何使用移步官网案例任玉刚博客),这篇文章主要解读编译系统根据aidl文件生成的java代码,目的是为了将来读懂ActivityManagerService的源码。

    AIDL生成代码解析

    为了便于阅读生成的代码,我们来写一个最最简单的AIDL,让服务端为我们实现两个数相加的功能。客户端界面如下:

    AIDL客户端界面 2017-09-12 18.01.31.png

    首先定义AIDL接口:

    // ICalculate.aidl
    package me.febsky.aidl;
    
    interface ICalculate {
        int add(int a, int b);
    }
    

    然后在AndroidStudio中运行Build-->Rebuild Project或者点击Gradle同步按钮,这时候会在app-->build-->generated-->source-->aidl下面生成ICalculate.java这个类。这些代码是自动生成的,不可修改,应该说改了也不起作用,下次编译还会被覆盖。

    AIDL代码生成位置 2017-09-12 18.11.05.png

    打开这个类文件,来看下生成的源码(为了便于阅读,在Mac上可以按command+alt + L来格式化代码),现在摘录代码如下:

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: /Users/liuqiang/Desktop/AIDL/app/src/main/aidl/me/febsky/aidl/ICalculate.aidl
     */
    package me.febsky.aidl;
    
    public interface ICalculate extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements me.febsky.aidl.ICalculate {
            private static final java.lang.String DESCRIPTOR = "me.febsky.aidl.ICalculate";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an me.febsky.aidl.ICalculate interface,
             * generating a proxy if needed.
             */
            public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
                    return ((me.febsky.aidl.ICalculate) iin);
                }
                return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
    
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(DESCRIPTOR);
                        return true;
                    }
                    case TRANSACTION_add: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();
                        int _arg1;
                        _arg1 = data.readInt();
                        int _result = this.add(_arg0, _arg1);
                        reply.writeNoException();
                        reply.writeInt(_result);
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements me.febsky.aidl.ICalculate {
                private android.os.IBinder mRemote;
    
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
    
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                }
    
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
    
                @Override
                public int add(int a, int b) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(a);
                        _data.writeInt(b);
                        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
            static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        public int add(int a, int b) throws android.os.RemoteException;
    }
    

    好吧,怪不得Android要搞出AIDL这么个东西,我在aidl中就写了一行代码,系统生成了这么多,要不是系统自动生成,每次用到Binder和AIDL进行进程间通信的时候,都要手撸这么写重复代码。

    这些代码从何看起呢,为了便于从宏观上观察生成的代码,我们折叠一下暂时不关心的代码:

    代码总览1 2017-09-12 18.17.35.png

    然后是这样:

    代码总览2 2017-09-12 18.19.25.png

    最后是这么个样子:

    代码总3 2017-09-12 18.23.30.png

    从以上代码来看,在生成的java文件中主要有三个类:ICalculateICalculate.StubICalculate.Proxy。其中Stub是接口ICalculate的静态内部类,Proxy是Stub的私有静态内部类(个人认为其实StubProxy没有必要一定要做为ICalculate的静态内部类,这样放置只是为了便于管理和查看他们之间的关联关系)

    ICalculate

    这个接口其实很简单继承与IInterface,先不用管这个IInterface的作用,只看ICalculate的话就是个普通的接口,这里面有我们定义的add方法,就是定了了我们要在AIDL中实现的业务逻辑。这个接口其实为了进程间通信,所有定义的是客户端需要服务端提供的功能。

    ICalculate.Stub

    这个类很重要,它继承了Binder类,实现了ICalculate接口。从继承关系来看他是一个具有ICalculate功能的Binder。好,既然是一个Binder就具有了进程间通信的功能。注意这个类是个抽象类,它只是定义了Binder的业务层通信功能,但是具体的通信内容(也就是我们的业务方法add方法)并没有具体实现,需要子类来实现。一般Stub的子类在服务端实现。

    说到这里必须说下Binder,Binder是Android上比较复杂的一个东西了。但这里我们不分析Binder的通信原理。只需要知道,Binder和BinderProxy是成对出现的,客户端进程持有BinderProxy对象,然后BinderProxy可以和binder驱动交互,binder驱动再去发消息给Binder对象从而实现IPC。个人认为为了便于理解,完全可以把BinderProxy和Binder类比成javaTCP 中的Socket和SocketServer

    看下Binder的源码结构:

    Binder 和BinderProxy 源码2017-09-13 14.30.06.png

    Binder和BinderProxy只是实现了进程间通信功能,具体通信内容是啥他不关心。通信内容交给ICalculate.Stub.Proxy 和 ICalculate.Stub的子类来实现。

    在ICalculate.Stub中有几个很重要的方法:

    • asInterface
    • onTransact

    首先看看asInterface方法,这个方法是一个静态方法,我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过这个方法拿到一个远程的service(这个Service不是Android的四大组件的那个Service)的代理(客户端和服务端不在同一个进程中的情况下),binderService时候的代码如下:

        ICalculate calculate;
    
        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                calculate = ICalculate.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
    //一般情况下,如果是跨进程的穿件来的参数都是BinderProxy类型的
    public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
        //这种情况基本不存在,可以忽略,你传了个null进来大家还玩啥?
        if ((obj == null)) {    
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        //这个if来判断,客户端和服务端是不是在一个进程中
        //也就是来判断,传进来的参数obj是Binder对象还是BinderProxy对象
        //如果在同一个进程中传入的是Binder对象,也即是Stub子类的对象,以下if语句成立
        if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
              return ((me.febsky.aidl.ICalculate) iin);
        }
        return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
    }
    

    onTransact后面我们和Proxy 中的transact方法一块分析。接下来先看Stub.Proxy这个类。

    ICalculate.Stub.Proxy

    Stub.Proxy 2017-09-13 15.35.57.png

    从代码中可以看出,这个类的构造方法接收一个IBinder的实现类,其实这里主要是BinderProxy的对象。然后忽略其他,直接看我们的add方法。前面也提到了,客户端通过Stub.asInterface 静态方法,持有Stub.Proxy 类的对象,然后和存在于服务端进程中Stub子类的对象进行通信。其实归根到底是客户端BinderProxy和服务端Binder的通信。
    这个add方法可以解读为,Stub.Proxy 类的对象,持有BinderProxy的对象,通过BinderProxy对象,像远程的Binder对象发送消息。看下发送消息的主要代码。可以先不用去管Parcel对象,可以把它看做一个可以序列化的对象,或者向远程发送数据的载体。把要传递给远程对象的参数放到Parcel中,然后调用BinderProxy的transact 方法,发送消息到Stub子类对象中。
    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
    这个方法由三个参数

    • Stub.TRANSACTION_add 方法名的唯一标示,告诉远程对象,我要执行你的哪个方法,目前我们的接口中只定义了一个add方法,
    • _data封装了要调用的远程方法的所有的需要的参数
    • _reply 远程方法返回值的载体
    • flag 最后一个参数是个flag,默认0就可以了,好像是用来指定是不是单向的IPC的

    通过以上过程,这样一个远程方法调用,就会通过Binder机制,把消息(调用某个方法)发送到服务端进程中的相应对象中。我们在此依然忽略BinderProxy和Binder之间跨进程通信的底层原理,只要知道,BinderProxy通过调用transact 方法,能通过Binder驱动,发消息到Binder进程就可以了。继续分析当BinderProxy通过transact 方法发送消息到服务端Binder子类对应的进程的时候,Stub的子类是如何接收处理这个消息的,看Stub类的onTransact方法:

    Stub中的onTransact 方法 2017-09-13 16.08.30.png

    可以看到在这个方法中有个switch语句,这个code就是刚刚在transact中的第一参数,用了标志该调用哪个方法。其余方法也和transact 中的一一对应,不再解释。其实上面的代码也很好理解,主要看第二个case里面语句吗,先把方法需要参数从data这个载体中读出来,对应 BinderProxy transact方法的写入操作,然后调用真正的业务方法addint _result = this.add(_arg0, _arg1);并把返回值写入到reply 这个返回值载体中从而能把方法返回值传递个客户端。从上面可以看到Stub是个抽象类,并没有实现业务方法add,这个要在他的子类中实现,具体代码如下:

    //注意这个Service要放到单独的进程中运行
    public class CalculateService extends Service {
    
        private ICalculate.Stub calculate = new ICalculate.Stub() {
            @Override
            public int add(int a, int b) throws RemoteException {
                return a + b;
            }
        };
    
        @Override
        public IBinder onBind(Intent intent) {
            return calculate;
        }
    }
    
    

    【[测试代码下载]
    (http://download.csdn.net/download/niyingxunzong/9977048)】

    测试效果图:

    测试效果图 2017-09-13 16.57.14.png

    重要知识点

    在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。

    其中基本的UML类图如下,类图中并没有标注出所有的方法,只是标注了我们关心的几个:

    UML类图.png

    相关文章

      网友评论

        本文标题:IPC -通过AIDL看Binder的进程间通信过程

        本文链接:https://www.haomeiwen.com/subject/uzagjxtx.html