Android AIDL 入门

Android AIDL 入门

Android 是一个基于 Linux 的操作系统,保留了多进程的特性。在 Android 中IPC是一个老生常谈的话题,每个进程都是一个单独的JVM虚拟机,它们是相互独立的,拥有自己特定的内存地址和空间,无法直接进行联系,而AIDL就像一座桥梁连接不同进程,桥制定了规则,规定人怎么来往,哪些人可以来往。

AIDL(Android Interface Definition Language),是一种IDL语言,定义Android中两个进程间通信的规则。其底层采用的是Binder机制,可以看做是Binder的官方封装框架

语法

AIDL语法和Java大致相似,但是也有着些许区别:

  • AIDL文件大概分为两类

    • 一类是定义parcelable对象,给其它AIDL文件使用AIDL支持的默认数据类型外的数据
    • 一类是用来定义接口的
  • 文件后缀名是.aidl

  • 支持的数据类型

    • Java八种基本数据类型(byte/short/int/long/float/double/boolean/char)
    • String 与 CharSequence 类型
    • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的
    • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的,Map是不支持泛型的。
    • 实现parcelable接口的对象
  • 目标文件即使与你正在编写的文件在同一个包下,也需要导包

  • AIDL中的定向 tag 表示了在跨进程通信中数据的流向

    • in 表示数据只能由客户端流向服务端,表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动
    • out 表示数据只能由服务端流向客户端,表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改,之后客户端将会同步变动
    • inout 则表示数据可在服务端与客户端之间双向流通,表现为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动

使用步骤

声明文件

创建AIDL文件,用来声明需要传输的数据,传输调用的接口,创建好后系统会自动生成一个默认示例方法,该方法展示了所支持的基本数据类型,一般直接删除然后声明自己需要的方法即可。
在这里插入图片描述

// Declare any non-default types here with import statements

interface IAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
  
    void log(String msg);
}

编译AIDL

我们所创建的AIDL文件其实并不是他最终的样子,使用Make Project 会将其编译成对应的 Java 文件。
在这里插入图片描述

public interface IAidlInterface extends android.os.IInterface
{
  /** Default implementation for IAidlInterface. */
  public static class Default implements com.whr.aidldemo.IAidlInterface
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.whr.aidldemo.IAidlInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.whr.aidldemo.IAidlInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.whr.aidldemo.IAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.whr.aidldemo.IAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.whr.aidldemo.IAidlInterface))) {
        return ((com.whr.aidldemo.IAidlInterface)iin);
      }
      return new com.whr.aidldemo.IAidlInterface.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_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.whr.aidldemo.IAidlInterface
    {
      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;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.whr.aidldemo.IAidlInterface sDefaultImpl;
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.whr.aidldemo.IAidlInterface impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.whr.aidldemo.IAidlInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

该类比较复杂,仅简单讲解,包含以下成员:

  • 静态匿名内部类 Default,是 AIDL 所定义接口的默认实现类
  • 抽象静态匿名内部类 Stub,继承自 Binder,实现 AIDL 定义接口。该类是 AIDL 最核心的一个类,AIDL 底层实现是基于 Binder,而具体的 Binder 实现代码就是在该类中被编译器自动补全的。

声明服务

AIDL 本质上只是一个接口,用于定义规则,而具体的实现就需要依赖于四大组件的Service了。由于是多进程交互,我们需要在Manifest中对该 Service 声明 process属性,以此在别的进程中创建该 Service,该属性有两种声明方式,一种是以:开头连接子进程名,这样声明出来的进程前缀会和应用包名保持一致。另一种则是完全重写,这样声明出来的进程名就是所声明的名字。

	        <service
                android:name=".RemoteService"
                android:enabled="true"
                android:exported="true"
                android:process=":remote">
            <intent-filter>
                <action android:name="com.whr.aidldemo.RemoteService" />
            </intent-filter>
        </service>

另外这里还加了一个intent-filter:这是为了隐式启动远程服务所用。

讲到intent-filter就得讲还有一个属性android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。手动设置false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。

创建服务

Service 是抽象类,当实现类继承它的时候需要实现其onBind() 方法,该方法返回IBinder对象,用于和绑定的Activity进行交互,通常在非IPC场景下随便声明一个Binder的实现类就可以了,而在这里,则需要用到之前声明的AIDL接口。

由于需要的是 IBinder对象,这样显然是不能将 AIDL 定义的接口直接进行实现后返回的,那要怎么办呢,这就涉及到 AIDL 文件自动编译后生成的抽象静态内部类 Stub了,对于该类的作用这里不过多介绍。总之该类继承自Binder并且要求实现 AIDL 定义好的接口,正好符合这里的需要。

public class RemoteService extends Service {

    private Binder stub = new com.whr.aidldemo.IMyAidlInterface.Stub() {

        @Override
        public void log(String msg) throws RemoteException {
            Log.i("common", "pid:" + Process.myPid() + "msg:" + msg);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
}

绑定服务

客户端绑定远程Service,并获得远程服务的代理对象,即获取远程Binder对象的接口的本地实现。
客户端便可以通过接口调用远程Service提供的方法。

因为不处于一个进程,推荐隐式启动,当前其实在同一个应用内不用隐式也可以正常启动。

注意在android5.0以前,我们只要设置Action就行了,但是之后需要把包名也一并设置,从源码可以看出如果启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常。

通过实现回调 ServiceConnection可以获得远程服务的IBinder对象,从而调用我们定义的方法,但是这里返回的引用是个顶层接口,无法知道我们定义的具体的能力,通常我们会想到用强转,但是当我们点进去看生成的 IMyAidlInterface会发现其根本不是IBinder的子类,因此肯定是不行的。

这里就需要用的Stub类了。

    private void initAIDL() {
        Intent intent = new Intent();
        intent.setAction("com.whr.aidldemo.RemoteService");
        intent.setPackage(getPackageName());
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
					IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {

            }
        }, Context.BIND_AUTO_CREATE);
    }

然后就可以调用我们定义好的方法进行跨进程交互了。

	        findViewById(R.id.log).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    myAidlInterface.log("click");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

打印出来的效果如下:

com.whr.aidldemo:remote I/common: pid:4021 msg:click

写在最后

总结一下,使用 AIDL 可以让我们非常方便的实现本来复杂的进程间通讯,主要包含以下步骤:

  • 声明 AIDL 接口。
  • 创建多进程服务并实现 AIDL 接口。
  • 绑定服务,通过 AIDL 生成的 Binder 实例进行多进程交互。

而这其中的难点其实也就是集中在系统编译 AIDL 之后,为我们自动实现的 Binder 那部分逻辑里面了,但这部分逻辑其实不用太过深究也能满足大部分的操作,想自己基于 Binder 实现跨进程交互的童鞋可以研究下这部分源码,本篇文章由于侧重点不同便不作解析。

遇到的坑

原本在一开始时笔者是准备用 kotlin 进行所以代码的编写的,但是写完在编译时发生了一个问题:kotlin 代码中的引用 AIDL 时文件资源找不到。

经过反复 clean 、build、清理缓存后依旧存在这个问题,去搜索了一波好像也没有别人遇到过这个问题,姑且就没有过多研究。如果有碰到过的童鞋希望能告知一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值