玩转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,总之在应用层,应该考虑尽量减少数据传输的负担。

相关推荐
雨白7 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹9 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空11 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭11 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日12 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安12 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑12 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟16 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡18 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0018 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体