• Android : 跟我学Binder --- (2) AIDL分析及手动实现



    目录:

    一、关于Android日常开发中进程间通信-AIDL

      通常Android应用开发中实现进程间通信用的最多的就是 AIDL,借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。但是无论是从可读性还是可理解性上来看,编译器生成的代码对开发者并不友好。比如一个 INanoMethod.aidl 文件对应会生成一个 INanoMethod.java 文件,这个 java 文件包含了一个 INanoMethod接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 INanoMethod的静态内部类,这就造成了可读性和可理解性的问题。

      INanoMethod.aidl 内容:

    package com.android.NanoServer;
    import com.android.NanoServer.INanoMethodCallback;
    // Declare any non-default types here with import statements
    
    interface INanoMethod {
        /**
         * 注册INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
        void registerCallBack( INanoMethodCallback callback);
        /**
         * 解除INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
        void unregisterCallBack(INanoMethodCallback callback);
        /**
         * 保存Activity对象.
         */
        void registerActivity();
    
    }

      通过编译器生产的 INanoMethod.java:

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: D:\work\Android\project\androidStudio\BLE-GATT-\aidl\src\main\aidl\com\android\NanoServer\INanoMethod.aidl
     */
    package com.android.NanoServer;
    // Declare any non-default types here with import statements
    
    public interface INanoMethod extends android.os.IInterface
    {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.android.NanoServer.INanoMethod
    {
    private static final java.lang.String DESCRIPTOR = "com.android.NanoServer.INanoMethod";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
    this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.android.NanoServer.INanoMethod interface,
     * generating a proxy if needed.
     */
    public static com.android.NanoServer.INanoMethod asInterface(android.os.IBinder obj)
    {
    if ((obj==null)) {
    return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.android.NanoServer.INanoMethod))) {
    return ((com.android.NanoServer.INanoMethod)iin);
    }
    return new com.android.NanoServer.INanoMethod.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
    {
    java.lang.String descriptor = DESCRIPTOR;
    switch (code)
    {
    case INTERFACE_TRANSACTION:
    {
    reply.writeString(descriptor);
    return true;
    }
    case TRANSACTION_registerCallBack:
    {
    data.enforceInterface(descriptor);
    com.android.NanoServer.INanoMethodCallback _arg0;
    _arg0 = com.android.NanoServer.INanoMethodCallback.Stub.asInterface(data.readStrongBinder());
    this.registerCallBack(_arg0);
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_unregisterCallBack:
    {
    data.enforceInterface(descriptor);
    com.android.NanoServer.INanoMethodCallback _arg0;
    _arg0 = com.android.NanoServer.INanoMethodCallback.Stub.asInterface(data.readStrongBinder());
    this.unregisterCallBack(_arg0);
    reply.writeNoException();
    return true;
    }
    case TRANSACTION_registerActivity:
    {
    data.enforceInterface(descriptor);
    this.registerActivity();
    reply.writeNoException();
    return true;
    }
    default:
    {
    return super.onTransact(code, data, reply, flags);
    }
    }
    }
    private static class Proxy implements com.android.NanoServer.INanoMethod
    {
    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;
    }
    /**
         * 注册INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
    @Override public void registerCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
    mRemote.transact(Stub.TRANSACTION_registerCallBack, _data, _reply, 0);
    _reply.readException();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    }
    /**
         * 解除INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
    @Override public void unregisterCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
    mRemote.transact(Stub.TRANSACTION_unregisterCallBack, _data, _reply, 0);
    _reply.readException();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    }
    /**
         * 保存Activity对象.
         */
    @Override public void registerActivity() throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    mRemote.transact(Stub.TRANSACTION_registerActivity, _data, _reply, 0);
    _reply.readException();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    }
    }
    static final int TRANSACTION_registerCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_unregisterCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_registerActivity = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }
    /**
         * 注册INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
    public void registerCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException;
    /**
         * 解除INanoVoiceCallback相关回调接口.
         *
         * @param INanoVoiceCallback 回调接口
         */
    public void unregisterCallBack(com.android.NanoServer.INanoMethodCallback callback) throws android.os.RemoteException;
    /**
         * 保存Activity对象.
         */
    public void registerActivity() throws android.os.RemoteException;
    }
    • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力,只要实现了这个接口,这个对象就能跨进程传输;

    • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力,即能提供哪些方法,对应的就是 AIDL 文件中定义的接口;

    • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理。这两个类都继承自 IBinder, 因而都具有跨进程传输的能力,实际在跨越进程时 Binder 驱动会自动完成这两个对象的转换。

    • Stub : 编写AIDL的时候,编译工具会生成一个名为 Stub 的静态内部类,这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力,Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

    编译工具根据定义的.aidl文件生成的.java代码虽然难于阅读,但是Android之所以这样设计并不无道理,因为当有多个 AIDL 文件的时候把 INanoMethod、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

    二、手动编写代码来实现跨进程调用

       一次跨进程通信必然会涉及到两个进程,在这个例子中 BookManagerService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 BookManagerService提供的服务。那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?前面介绍过的 IInterface 代表的就是服务端进程具体什么样的能力,即提供了什么功能的接口。因此需要定义一个 BookManager 接口,BookManager 继承自 IInterface ,表明服务端具备什么样的能力:

    /**
     * 这个类用来定义服务端 BookManagerService 具备什么样的能力
     */
    public interface BookManager extends IInterface {
      //提供添加书籍接口
    void addBook(Book book) throws RemoteException; }

      只定义服务端具备什么样的能力是不够的,既然是跨进程调用,那需要实现一个跨进程调用对象 Stub。Stub 继承自 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现:

    public abstract class Stub extends Binder implements BookManager {
    
        ...
        
        public static BookManager asInterface(IBinder binder) {
            if (binder == null)
                return null;
            IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
            if (iin != null && iin instanceof BookManager)
                return (BookManager) iin; //如果client和server在同一进程,则返回本地对象
            return new Proxy(binder);  //如果client和server不在同一进程,则创建并返回代理对象
        }
    
        ...
    
        @Override
      /**
       *  code:每个方法都有一个int类型的数字(编号)用来区分
       *  data: 传过来的数据,其中包含参数,以及类的描述。
       *  reply:传回的数据,要写入是否发生了Exception,以及返回值。
       *  flags:该方法是否有返回值 ,0表示有返回值。
       */
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
    
                case INTERFACE_TRANSACTION:
                    reply.writeString(DESCRIPTOR);
                    return true;
    
                case TRANSAVTION_addBook:
                    data.enforceInterface(DESCRIPTOR);
                    Book arg0 = null;
                    if (data.readInt() != 0) {
                        arg0 = Book.CREATOR.createFromParcel(data); 
                    }
                    this.addBook(arg0); //调用本地.java中实现的方法
                    reply.writeNoException();
                    return true;
    
            }
            return super.onTransact(code, data, reply, flags);
        }
    ...
      static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //转换成对应编号
    }

      接下来就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口:

    public class Proxy implements BookManager {
        
        ...
    
        public Proxy(IBinder remote) {
            this.remote = remote;
        }
    
        @Override
        public void addBook(Book book) throws RemoteException {
    
            Parcel data = Parcel.obtain();
            Parcel replay = Parcel.obtain();
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                if (book != null) {
                    data.writeInt(1);
                    book.writeToParcel(data, 0);
                } else {
                    data.writeInt(0);
                }
                remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0); //调用Stub的onTransact方法中对应函数
                replay.readException();
            } finally {
                replay.recycle();
                data.recycle();
            }
        }
    
        ...
    }

      以上关于 Stub 类中重点介绍下 asInterface 和 onTransact

    • asInterface : 当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传上来的,正如上代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy,因此需要创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。
    • onTransact : 在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回,驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact(),最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数,这里要提及一下,在跨进程调用的时候不会传递函数名而是传递编号来指明要调用哪个函数,如果提供给client端的aidl文件接口数量或者顺序和server端的声明不一样,则会导致函数调用错位的问题。

        在client端绑定服务及使用接口:

      ......
      /**
    * 尝试与服务端建立连接 */ private void attemptToBindService() { Intent intent = new Intent(); intent.setAction("com.android.aidl.server"); intent.setPackage("com.android.NanoServer"); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBookManagerService = BookManager.Stub.asInterface(service); //获得代理对象 if (BookManagerService != null) { mBound = true; } } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; ...... //成功绑定服务后便可调用服务端提供的接口 try { if (mBookManagerService != null) {     mBookManagerService.addBook(book); } } catch (RemoteException e) { e.printStackTrace(); }

    注:

    • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
    • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

    -end-

  • 相关阅读:
    JS 字符串
    JS 变量
    JS 数据类型与运算符
    HTML加载动画实现
    DOM Document.readyState 属性
    html中怎么去掉input获取焦点时候的边框
    原生js获取子元素
    CSS3 Animation动画
    slice,substr和substring的区别
    a链接嵌套无效,嵌套链接最优解决办法
  • 原文地址:https://www.cnblogs.com/blogs-of-lxl/p/10123488.html
Copyright © 2020-2023  润新知