Android中跨进程通信之AIDL
Android 跨进程通信的方式一般有:
- Binder
- 内存共享
- 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
}
}
遇到的问题或疑惑:
通过API30 的x86 虚拟机可以正常唤醒service并数据正常传输,但是在API33的真机上无法bind service
通过查阅资料,发现在30或以上的API level后存在Package visibility filtering on Android 这么一个说法。
大概就是为了更高的安全性而加入了过滤行为,从而导致应用无法检测到当前设备上所有安装的应用程序。解决方法加入queries
<queries> <package android:name="com.easy.aidlserver" /> </queries>
已经操作了unbindService,但是仍然能进行数据操作,而且日志走了onUnbind却不走onServiceDisconnected
其实onServiceDisconnected在正常unbind的时候是不会触发执行的,而是在服务丢失的时候执行。传输大数据的时候
因为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) ) )
你会收到报错信息: