AIDL的生成与调试

AIDL简介

AIDL:AIDL=Android Interface definition language(Android 接口定义语言),与您可能使用过的其他 IDL 类似。 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。

摘自google开发者文档:https://developer.android.com/guide/components/aidl.html?hl=zh-cn

AIDL的作用

  • 用一套固定的模式,帮助android开发者实现跨进程(RPC)调用
  • 自动生成基于client & server端所需要的代码
  • 底层基于Binder实现

最重要的是方便,同时通过编译工具生成对应的代码,极大简化了java码农进行RPC开发

AIDL的一些基本ABC

google开发者文档已经介绍的非常详细了,这边稍微摘录一些。
传送门:https://developer.android.com/guide/components/aidl.html?hl=zh-cn#CreateAidl

1. 创建 .aidl 文件

AIDL 使用简单语法,使您能通过可带参数和返回值的一个或多个方法来声明接口。
参数返回值可以是任意类型,甚至可以是其他 AIDL 生成的接口
您必须使用 Java 编程语言构建 .aidl 文件。每个 .aidl 文件都必须定义单个接口,并且只需包含接口声明和方法签名。
以下是一个 .aidl 文件示例:

// IRemoteService.aidl
package com.example.android;

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

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** 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);
}

2. 实现接口

当您开发应用时,Android SDK 工具会生成一个以 .aidl 文件命名的 .java 接口文件。生成的接口包括一个名为 Stub 的子类,这个子类是其父接口的抽象实现,用于声明 .aidl 文件中的所有方法。
以下是一个使用匿名实例实现名为 IRemoteService 的接口(由以上 IRemoteService.aidl 示例定义)的示例:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};
  • 在实现 AIDL 接口时应注意遵守以下这几个规则:
    • 由于不能保证在主线程上执行传入调用,因此您一开始就需要做好多线程处理准备,并将您的服务正确地编译为线程安全服务。
    • 默认情况下,RPC 调用是同步调用。如果您明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做 可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 您通常应该从客户端内的单独线程调用服务。
    • 您引发的任何异常都不会回传给调用方。

3. 向客户端公开该接口

您为服务实现该接口后,就需要向客户端公开该接口,以便客户端进行绑定。 要为您的服务公开该接口,请扩展 Service 并实现 onBind(),以返回一个类实例,这个类实现了生成的 Stub(见前文所述)。以下是一个向客户端公开 IRemoteService 示例接口的服务示例。

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

不一样的AIDL分析

上一章节的内容是最基本的AIDL使用守则,在进入本章节前请确保已经了解了AIDL/Binder的基本用法,并且知道进程和调试的基本信息。
基于上述的:IRemoteService,我们写了一个hellowrold

IDE工具的编译

如果是使用android studio进行aidl编译的话,可以从compileDebugAidl看出对应的编译指令:

D:\software\AndroidStdio\sdk\build-tools\25.0.2\aidl.exe
-pD:\software\AndroidStdio\sdk\platforms\android-25\framework.aidl
-oZ:\code\demo\app\build\generated\source\aidl\debug
-IZ:\code\demo\app\src\main\aidl
-IZ:\code\demo\app\src\debug\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\appcompat-v7\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-v4\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-fragment\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-media-compat\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-core-ui\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-core-utils\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\animated-vector-drawable\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-vector-drawable\25.3.0\aidl
-IZ:\code\demo\app\build\intermediates\exploded-aar\com.android.support\support-compat\25.3.0\aidl
-dC:\Users\AppData\Local\Temp\aidl6189821749730508340.d
Z:\code\demo\app\src\main\aidl\android\example\com\IRemoteService.aidl

因此,其实是IDE工具帮忙做了AIDL文件的转换,如果有时候AIDL编译出错,也可以手动调用IDE工具自行生成或者是查看log定位问题。

阅读auto-generated AIDL

前面有说到,AIDL文件会通过IDE工具生成java文件。
这个文件一般会生成在out/build目录下,文件名相同,只是后缀会改成java。
例如,demo中的文件目录:app\build\generated\source\aidl\debug\com\example\android\IRemoteService.java

IRmoteService.java整体上还是保存了IRemoteService.aidl的面貌

如图所示,我们折叠了Stub部分的实现,先看IRemoteSerivce的本体:

  • IRmoteService继承了android.os.IInterface,这个接口提供了IBinder asBinder();的能力

    根据我们对java层Binder的认识,可以了解到这个asBinder()绝大对数都是提供了一个Proxy
    即,client端的app是通过Proxy来与远端的实体Server建立联系

下面我们展开Stub看看具体的实现:

  1. Sub继承自android.os.Binder,因此会具备RPC能力,其中asBinder会决定具体的IBinder对象是谁
  2. asInterface会负责做强制转换,目的是把各类IBinder对象转换成具体的子类
  3. 借助Proxy内部类,返回对应的IRemoteService对象
  4. Stub内部再次嵌套了一个Proxy类,后面再次展开了看
  5. asBinder的庐山真面目,这里直接return this,也即Stub本身即是Binder对象,也是Server实体
  6. onTransat这个函数是典型的Binder通讯专有api,而onTransat通常是Server端

综上所述,我们还得再看一眼Proxy

  • Porxy实现了IRemoteService的接口,这一点与Stub无异,但是Stub是继承自android.os.Binder
  • 针对每一个具体的api,比如getPid而言,其实都是通过mRemmote重新包装了一下,并透过transact转发

    那么问题就来了,这边transact会到哪里呢?
    关于上面的问题可以参考:transact和onTransact的区别

小结:

  • AIDL->Java,IDE会帮我们完成,真正参与编译的还是java代码
  • 同名的AIDL文件,会带有3个class,即IRemoteServiceStubProxy
  • Stub是transact的受理方,所以它是服务(Server),它需要实现onTransact,它继承自Binder,并需要实现IXXXX定义的api
  • Proxy是transact的申请方,所以它是代理,是客户端(Client),它拥有IBinder,仅实现了IXXXX定义的api

最后,列一下对应的class关系UML图:

从图中,还有两个隐藏的点

  • Stub是abstract的类,所以它需要被继承
  • Proxy是具类,所以可以直接使用

调试

  • 你真的了解了AIDL的生成以及上文提到的transact以及onTransact的关系。
  • 你真的了解对应代码运行的环境和所处的进程

结合《Android Frameworks代码调试》,相信debug起来是非常简单的。
一般来说,我们在debug AIDL文件的时候,需要把对应生成的java文件一并放到IDE中,这个方法有很多,直接裸打开也是可以的。
然后找到对应的进程,通过debug attach上进程,找到对应地方打上断点。

如果你发现断点不生效,那么一定是进程attach错了,所以回到上文

  • 你真的了解对应代码运行的环境和所处的进程 ?!!

一点小扩展

如果你真的懂了上面所说的,那我们来找个例子。

以AMS为例,其中startActivity的一个调用流程。
AMS有一个特点,虽然它没有对应的AIDL,但是整段代码其实是由AIDL转换为JAVA后的JAVA最终形态。
也就是说,AMS是裸写的一个Binder RPC。我们通过UML图来看看AMS相关的类是什么关系。

  • 结合之前提到的那些点,我们来看看
  • 首先IActivityManager,可以认为是之前的IRemoteService.aidl生成的IRemoteService
  • 其次,看继承关系数,ActivityManagerNative继承了Binder,同时还实现了IActivityManager

    这不妥妥的就是IRemoteService.Stub吗?

  • 对应的,ActivityManagerProxy则是直接实现了IActivityManager

    这不妥妥的就是IRemoteService.Proxy吗?

如果要debug整个startActivity你要怎么做呢?
首先,我们确认了整个startActivity是从app端发起的,最后是在AMS内部处理的。
所以我们打断点的时候,java层app的终点,是在ActivityManagerProxy中的:
image-20221010165339992
对应的,server端的起点,是在ActivityManagerNativeonTransact中的:
image-20221011093519388