Android AIDL通信,及其Binder通讯原理

1.为什么试用aidl,而不是直接通信?

  • 一个进程空间分为:用户态(用户空间)和内核态(内核空间),即把进程内用户和内核隔离开来
  • 进程之间,由于Android系统为每个进程分配了一个独立的虚拟机,用户空间和内核空间的数据不可交互
  • Binder作为进程间的介质,充当了中介,使得进程间的内核态可以通过Binder进行数据交互

2.通信过程

  • 首先编写以“aidl”结尾的aidl文件,编译生成同名的java文件,在app/build/generated/source/aidl 目录下
  • 打开aidl生成的java文件查看主要类当前类实现了IInterface接口,同时生成的Stub抽象类(运行在Service端),以及Stub抽象类的内部类 Proxy代理类(运行在Client端)
  • 开始通信,通过自定义一个方法,例如 call(String msg)
private android.os.IBinder mRemote;
@Override
            public java.lang.String call(java.lang.String req) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(req);
                    // 调用了IBinder 的transact
 //  第一个参数表示这一次请求执行的意图,IBinder定义了像
 //	INTERFACE_TRANSACTION、PING_TRANSACTION这样的几个通用命令。
 //	自己使用的命令的标识值需要在FIRST_CALL_TRANSACTION和LAST_CALL_TRANSACTION之间。
 //	这个参数是客户端和服务端约定好的。
//      第二个参数表示向服务端发送的数据,不能为null。
//      第三个参数表示服务端返回的数据,可以为null。
//      第四个参数flags只有0和FLAG_ONEWAY (=1)两种,默认的跨进程操作是同步的,所以transact()方法的执行会阻塞flasg=0;
//指定FLAG_ONEWAY时,表示Client的transact()是单向调用,执行后立即返回,无需等待服务端返回。
                    mRemote.transact(Stub.TRANSACTION_call, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

**注意:**执行 transact 后当前线程就会被阻塞,直到服务端返回结果,如果耗时太长,需要我们在子线程开启通信
再进入查看 IBinder 的 transact是空方法,查找子类Binder

public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

可以看到最终调用了Binder的onTransact 方法,再查看我们的Stub抽象类,继承了Binder类,重写了onTransact方法

@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_call: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _result = this.call(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

根据code不同执行不同的方法,然后将数据写入reply,最后Proxy代理类收到数据,线程继续向下执行。
最后附Binder路径: platform_frameworks_base/core/java/android/os/
2019.05.15更新,摘录 《Android 内核剖析》——柯元旦 著

Binder 是一种架构,这种架构提供了服务端接口、BInder驱动、客户端接口三个模块

  1. 首先看服务端,一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的 onTransact 函数,所以服务端实现Binder,就必须重载 onTransact方法
  2. 重载 onTransact方法主要内容是把 onTransact方法的参数转换为服务函数的参数,而 onTransact方法 的参数正是由客户端调用transact方法输入的,
  3. Binder驱动,任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRomete对象,该对象的类型也是Binder类,客户端要访问远程服务时,都是通过mRomete对象
  4. 客户端 要访问远程服务,必须获取远程服务在Binder对象中对应的mRomete引用,如何获取?获得该mRomete对象后,就可以调用其 transact()方法,而在Binder驱动中,mRomete对象也重载了transact()方法,重载的主要内容包括以下几项
    (1) 以线程间消息通信的模式,想服务端发送客户端传递过来的参数。
    (2)挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行指定服务函数后通知(notify)
    (3)接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区

从这里可以看出,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。
即 存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程
所以,客户端发出请求——> Binder驱动——>Binder(服务端)响应

如何获取远程服务在Binder对象中对应的mRemote引用?
绑定服务需要一个 ServiceConnection 接口

public interface ServiceConnection {
    void onServiceConnected(ComponentName name, IBinder service);
    void onServiceDisconnected(ComponentName name);
    }

该接口的 onServiceConnected 方法的第二个变量 service 当客户端请求AMS启动某个Service后,该 Service如果正常启动,那么AMS 就会在远程调用ActivityThread 类中的ApplicationThread 对象,调用的参数中会包含Service的Binder引用,然后在Application中会回调binderService 中的conn接口,因此在客户端中,可以在 onServiceConnected方法中将其册数service 保存一个全局变量,从而在客户端的任何地方都可以随时调用改远程服务这就解决了一个重要问题,即客户端如何获取远端服务的Binder引用。

一次完整的通信
Proxy 将我们的请求参数发送给ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,
执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命
令,并没有拷贝数据,binder 只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成
了一次通讯。

参考文档
写给 Android 应用工程师的 Binder 原理剖析
Android深入浅出之Binder机制
Android进程间通信(IPC)机制Binder

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值