用 AIDL 实现 Android 跨进程通信

368 阅读3分钟

我们知道,在 Android 中,一个进程一般是无法访问另一个进程内存的,不同进程之间的通信就可以使用 AIDL 来处理,下面就一起来瞧瞧吧 ~

什么是 AIDL

AIDL 是 Android 接口定义语言,是 Android 提供的一种进程间通信 (IPC) 机制,它能生成可以在 Android 设备上两个进程之间进行进程间通信的代码。通过 AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。

支持的数据类型

  • Java 中的八种基本数据类型:byte,short,int,long,float,double,boolean,char。
  • String,charSequence,List,Map,其中 List 和 Map 中所有元素都必须是 AIDL 支持的数据类型。
  • 自定义数据类型。

同个应用不同进程之间的通信

我们先来创建一个 AIDL 文件

1.png 然后,定义一下这个 IPC 接口,这里只是简单地定义了一个方法,用于获取数据,定义之后 Rebuild 一下,它会自动生成一些相关的代码。

interface DataAidlInterface {

    String getData();
}

然后我们创建一个 Service,用于对外提供具体的业务,这里为该 Service 单独开一个进程。

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":ipc" />
class MyService : Service() {

    private var myData = "ipc service data"

    private val binder = object : DataAidlInterface.Stub() {

        override fun getData() = myData

    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

Service 创建完之后,接下来就是 Client 调用了。

private var dataAidlInterface: DataAidlInterface? = null

private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
        //拿到 AIDL 对象进行远程调用
        dataAidlInterface = DataAidlInterface.Stub.asInterface(p1)
        //查看 Service 暴露出来的数据
        Log.i(tag, "onServiceConnected: ${dataAidlInterface?.data}")
        unbindService(this)
    }

    override fun onServiceDisconnected(p0: ComponentName?) {
        Log.i(tag, "onServiceDisconnected")
    }
}
val intent = Intent(context, MyService::class.java)
//绑定 Service
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

当然,在日常开发中,我们的数据来源一般不会这么简单,可能是一个网络请求返回等耗时的操作,这样的话,我们就需要定义 Callback 来处理结果回调了。

创建两个 AIDL 文件,一个用于将执行方法暴露出去,一个用于结果回调。

interface DataAidlInterface {

    void getNetData(NetDataCallback callback);
}
interface NetDataCallback {
    void onSuccess(String data);
    void onError(String msg);
}

这里延时两秒来模拟耗时操作

class MyService : Service() {

    private val binder = object : DataAidlInterface.Stub() {
        override fun getNetData(callback: NetDataCallback?) {
            CoroutineScope(Dispatchers.IO).launch {
                delay(2000)
                //耗时结束,返回数据
                callback?.onSuccess("ipc net data")
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

然后客户端就可以调用,通过接口回调拿到耗时操作后的结果。

private var dataAidlInterface: DataAidlInterface? = null

private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
        dataAidlInterface = DataAidlInterface.Stub.asInterface(p1)
    }

    override fun onServiceDisconnected(p0: ComponentName?) {
        Log.i(tag, "onServiceDisconnected")
    }
}

获取数据

dataAidlInterface?.getNetData(object : NetDataCallback.Stub() {
    override fun onSuccess(data: String?) {
        //查看 Service 暴露出来的数据
        Log.i(tag, "ipc data: $data")
        unbindService(serviceConnection)
    }

    override fun onError(msg: String?) {
        unbindService(serviceConnection)
    }
})

不同应用之间的通信

如果现在有两个 APP,一个 APP 需要调用另一个 APP 程序的方法,这种跨进程处理起来跟上面还是有些许不同的,主要是权限方面的问题,另外,AIDL 还可以传递对象,但是该类必须实现 Parcelable 接口,这里一并讲讲。

plugins {
    ...
    id 'kotlin-parcelize'
}
@Parcelize
data class User(
    var name: String,
    var location: String,
    var age: Int
) : Parcelable

这样一来,这个对象就可以传递了,然后我们创建 AIDL 文件,Rebuild 一下。

parcelable User;

interface IMyAidlInterface {

    User getUserData();
    void jumpPage();
}

创建 Service 把方法暴露出去,提供给别的应用调用。

<service
    android:name=".IPCService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.myapplication.aidl"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>
class IPCService : Service() {

    private val binder = object : IMyAidlInterface.Stub() {
        override fun getUserData(): User {
            return User("Tom", "China", 26)
        }

        override fun jumpPage() {
            //启动一个 Activity
            val intent = Intent(this@IPCService, MainActivity::class.java)
            intent.flags = FLAG_ACTIVITY_NEW_TASK
            startActivity(intent)
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

好了,现在别的应用就可以调用了,现在我们再新建一个 Project 作为别的应用来调用它。但是,调用方必须有权限,需要在 AndroidManifest 中加入必要的权限。

<queries>
    <package android:name="com.example.myapplication" />
    <intent>
        <action android:name="com.example.myapplication.aidl" />
    </intent>
</queries>

然后,我们需要把 Service 端的整个 AIDL 文件复制到 Client 端,因为我们需要保证客户端和服务端的 AIDL 包名一致,不然会如下报异常:

java.lang.SecurityException: Binder invocation to an incorrect interface

除此之外,因为这里涉及到一个 User 类,它也需要从 Service 端复制到 Client 端,它也需要保证包名一致,不然会找不到这个类,编译不通过,这也是我遇到的一个坑。

现在调用方就可以开始跨应用调用了

private var iMyAidlInterface: IMyAidlInterface? = null

private val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
        iMyAidlInterface = IMyAidlInterface.Stub.asInterface(p1)
        Log.i(tag, "ipc data: ${iMyAidlInterface?.userData}")
        iMyAidlInterface?.jumpPage()
        unbindService(this)
    }

    override fun onServiceDisconnected(p0: ComponentName?) {
        Log.i(tag, "onServiceDisconnected")
        iMyAidlInterface = null
    }

}

绑定别的应用的 Service

val intent = Intent().apply {
    action = "com.example.myapplication.aidl"
    `package` = "com.example.myapplication"
}
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

这样,我们就可以通过调用别的应用的方法来获取别的应用的数据或启动别的应用的页面了,不管被调用方在不在后台,都是可以的。