Android中跨进程通信之AIDL

Android 跨进程通信的方式一般有:

  1. Binder
  2. 内存共享
  3. Socket

其中Binder在Android中使用比较多,比如AMS
在Android上,一个进程通常是不能获取到其他进程的内存访问权限的,但是有时我们需要在多个进程之间进行数据处理,因此Android提供了Android Interface Definition Language(AIDL)来做处理
其实AIDL是文件会通过编译生成一个Sub类并实现IBinder接口的,这种方式可以说是基于Binder实现的。

配置新项目

首先创建一个项目,有两个Phone Module,分别为Client和Server端。对于新的项目打开aidl feature支持

buildFeatures {
    compose true
    aidl true
}

Server端:

然后创建aidl文件

interface IRemoteAidlInterface {
    /**
    * Demonstrates some basic types that you can use as parameters
    * and return values in AIDL.
    */
    int currPid();
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
    double aDouble, String aString);
    void addUser(inout User user);
    User theFirstUser();
}

以上加了User不是基本类型,所以需要做一些特殊处理,
首先添加一个aidl文件声明下这个类

package com.easy.aidlserver;

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

parcelable User;

然后创建一个data class, 并添加readFromParcel方法

@Parcelize
data class User(
var name: String? = "",
var age: Int = 0
): Parcelable {
    fun readFromParcel(reply: Parcel) {
        this.name = reply.readString().toString()
        this.age = reply.readInt()
    }
}

Build下项目生成对应的java类,
声明并注册一个Service,提供数据处理能力

class RemoteService : Service() {
    override fun onBind(intent: Intent?): IBinder {
        Log.d("Service Side", "onBind");
        return binder;
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.d("Service Side", "onUnBind")
        return super.onUnbind(intent)
    }

    private val users = mutableListOf<User>()

    .....
}

并在manifest种注册

<service
    android:name=".RemoteService"
    android:exported="true"
    tools:ignore="ExportedService">
    <intent-filter>
        <action android:name="com.aidl.server" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

这里Server端代码完成了

Client端

copy aidl文件到client端,注意要相同的包名和内容,还有对应的data class User
定义connection并通过Binder拿到AIDL Interface,拿到interface就可以通过调用aidl提供的方法,并通过Proxy和服务端的service进行数据传输和处理

private val sConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        iRemoteService = IRemoteAidlInterface.Stub.asInterface(service)
        iRemoteService?.let {
            try {
                val pid = it.currPid()
                val myPid = Process.myPid()
                Log.i("Client", "Service Pid: $pid, Client Pid: $myPid")
                it.basicTypes(1, 2, true, 3.0f, 5.0, "Hello AIDL Server")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        Log.d("Client", "onDisconnect")
        iRemoteService = null
    }
}

遇到的问题或疑惑:

  1. 通过API30 的x86 虚拟机可以正常唤醒service并数据正常传输,但是在API33的真机上无法bind service
    通过查阅资料,发现在30或以上的API level后存在Package visibility filtering on Android 这么一个说法。
    大概就是为了更高的安全性而加入了过滤行为,从而导致应用无法检测到当前设备上所有安装的应用程序。解决方法加入 queries

    <queries>
        <package android:name="com.easy.aidlserver" />
    </queries>
  2. 已经操作了unbindService,但是仍然能进行数据操作,而且日志走了onUnbind却不走onServiceDisconnected
    其实onServiceDisconnected在正常unbind的时候是不会触发执行的,而是在服务丢失的时候执行。
    When to excute onServiceDisconnected

  3. 传输大数据的时候
    因为AIDL 是通过IBinder进行数据传输的,而IBinder对数据的拷贝是经过mmap(内存映射),在我们app启动的时候,会去申请一个 1M - 8K大小的内存给mmap,所以如果
    传输1M或以上的数据就会报错。
    将User修改一下,加一个ByteArray

    @Parcelize
    data class User(
        var name: String? = "",
        var age: Int = 0,
        var byteArray: ByteArray = ByteArray(1)
    )

    使用的时候

    // _1M = 1024 * 1024
    iRemoteService?.addUser(
        User(
            "Dougie ${Random.nextInt(1, 100)}",
            Random.nextInt(18, 24),
            byteArray = ByteArray(_1M)
        )
    )

    你会收到报错信息:
    android.os.TransactionTooLargeException: data parcel size 1048712 bytes

代码地址 AidlSample