玩转Android Framework:AIDL

本文将介绍有关AIDL相关的一些知识。

系统版本: Ubuntu 22.04 lts

AOSP分支: android-14.0.0_r28

什么是AIDL

AIDL,全称 Android Interface Definition Language,是一种接口定义语言,这里是关于AIDL的官方文档:

developer.android.com/develop/bac...

为什么我们需要AIDL

要回答这个问题,要先看看AIDL替我们做了什么,我们先创建一个IRemoteService.aidl,内容如下:

Java 复制代码
interface IRemoteService {
    void Wow(inout int[] test);
}

然后创建一个Service,实现所生成的Stub:

Kotlin 复制代码
class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()

        bindService(Intent(this, RemoteService::class.java), object : ServiceConnection{
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {

            }

            override fun onServiceDisconnected(name: ComponentName?) {

            }

        }, BIND_AUTO_CREATE)
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun Wow(test: IntArray) {

        }
    }
}

然后进入IRemoteService,可以看到基于我们所提供的AIDL文件,为我们生成的代码:

Java 复制代码
public interface IRemoteService extends android.os.IInterface
{
  /** Default implementation for IRemoteService. */
  public static class Default implements com.example.aidlapplication.ui.theme.IRemoteService
  {
    @Override public void Wow(int[] test) 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.example.aidlapplication.ui.theme.IRemoteService
  {
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.aidlapplication.ui.theme.IRemoteService interface,
     * generating a proxy if needed.
     */
    public static com.example.aidlapplication.ui.theme.IRemoteService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.aidlapplication.ui.theme.IRemoteService))) {
        return ((com.example.aidlapplication.ui.theme.IRemoteService)iin);
      }
      return new com.example.aidlapplication.ui.theme.IRemoteService.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;
      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
        data.enforceInterface(descriptor);
      }
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
      }
      switch (code)
      {
        case TRANSACTION_Wow:
        {
          int[] _arg0;
          _arg0 = data.createIntArray();
          this.Wow(_arg0);
          reply.writeNoException();
          reply.writeIntArray(_arg0);
          break;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
      return true;
    }
    private static class Proxy implements com.example.aidlapplication.ui.theme.IRemoteService
    {
      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 void Wow(int[] test) 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.writeIntArray(test);
          boolean _status = mRemote.transact(Stub.TRANSACTION_Wow, _data, _reply, 0);
          _reply.readException();
          _reply.readIntArray(test);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
    }
    static final int TRANSACTION_Wow = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  }
  public static final java.lang.String DESCRIPTOR = "com.example.aidlapplication.ui.theme.IRemoteService";
  public void Wow(int[] test) throws android.os.RemoteException;
}

这些代码,理论上来讲,我们都可以自己写,即完全抛弃AIDL,无论客户端或者服务端,我们都自己实现所有的传输逻辑,但是你可以看到,这么简单的一个方法就需要我们写这么多的代码,尤其传输数据的部分,我们需要使用Parcel去顺序的进行读写,如果手写这部分代码,就十分容易弄错,而且还要服务端和客户端进行协同,如果我们有很多的Service,这些Service需要Binder暴露更多的方法给客户端,那么我们需要手写的代码将会增加多少可想而知,而且还要通知客户端及时更新对应的逻辑,这样就十分复杂。

但是通过AIDL,我们可以简化这些工作,在Android源码中,我们可以看到大量的AIDL文件,可以说AIDL大大降低了应用层使用Binder通讯的重复和复杂性。

AIDL模板代码如何被生成

在Gradle编译过程中,实际上是使用了Android SDK提供的aidl工具去根据aidl文件去生成模板代码的,打开/Sdk/build-tools/34.0.0/,可以看到aidl程序:

运行此程序,可以看到,aidl不光可以生成java代码,C++甚至Rust代码都可以生成:

in, out, inout和oneway关键字

在AIDL中,我们可以使用onewayinoutinout等关键字来修饰我们的方法或参数,下面我将讲解这些参数代表着什么。

in,out,inout

当我们编写AIDL文件时,如果方法的参数为非基础类型参数,那么就会提示我们要标记参数的类型,否则会报错,比如下面的AIDL:

Java 复制代码
interface IRemoteService {
    void Wow(int[] test);
}

编译的时候会提示错误:

当我们把参数用inout或者inout标记以后,就不会报错了:

Java 复制代码
interface IRemoteService {
    void Wow(inout int[] test);
}

那么inoutinout实际指代什么呢? 它其实代表着我们的数据流向。

in表示数据只能由客户端流向服务端,用in标识的参数,当服务端修改此参数时,客户端读取到的参数仍然是原来的值,不会被修改。

out表示数据只能由服务端流向客户端,用out标识的参数,服务端接到的参数会是空对象,而服务端修改此参数的时候,客户端能读取到修改后的值。

inout则表示数据会双向流动,即客户端和服务端对参数的修改,彼方都可以接收到。

oneway

我们可以使用oneway来修饰我们的Wow方法,注意,由oneway修饰的方法,非基础类型参数必须标记为in:

Kotlin 复制代码
interface IRemoteService {
    oneway void Wow(in int[] test);
}

然后当我们的客户端在调用Wow时,就不会等待服务端的处理,会立刻进行下一步,不再等待服务端的处理。

为什么需要这些关键字

现在,我们理解了这些关键字的作用,那么问题来了,为什么需要这些关键字? 比如方法为什么需要设置为oneway,为什么参数不全设置成inout

要理解这个问题,首先就要理解,Binder通讯的本质是什么,Binder通讯的本质,就是基于Binder驱动的一种进程间通讯,也就是说,我们最终数据的传递是需要Native层控制Binder驱动替我们去处理的,那么这些关键字最终影响的,其实就是我们调用Binder驱动传递数据的方式和逻辑,比如打开/frameworks/native/libs/binder/IPCThreadState.cpp,找到transact方法,就可以看到在调用写入数据writeTransactionData之后,如果被标记为oneway,那么就会向waitForResponse传入空的参数,在waitForResponse中其实就可以看到,在数据传输之后就会直接退出循环,在Java层的表现就会变成调用被oneway标识的方法之后,没有阻塞立刻执行下一步:

我们该什么时候使用这些关键字

使用这些关键字的自由度,其实是比较高的,但是秉承的原则应该是非必须原则,比如客户端如果想控制一些服务端的状态,比如setProperty1(boolean),那么这种方法就可以标记为oneway,因为这类方法一般不需要服务端的返回,同样的,有一些参数是客户端希望服务端去设置并读取的,那么就可以标记为out,总之在应用层,应该考虑尽量减少数据传输的负担。

相关推荐
千里马学框架8 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台8 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone9 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc9 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo11 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770011 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼12 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone13 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen13 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone13 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui