新建个 AIDLService 项目,用于做服务端。
在项目中选择新建 AIDL 文件时可能会如下(图2)所示:
为了能够正常的创建aidl文件,我们只需要在 build.gradle 的 android 中增加以下代码即可,记得执行同步gradle操作。
buildFeatures {
aidl true
}
此时通过 AS 的快捷菜单我们就可以快速新建 aidl 文件了。如下图我选择新建一个 IMyServer.aidl。
点击完成后在项目的aidl文件夹中自动生成一个aidl文件。
在 IMyServer.aidl 中定义了两个方法。注意。aidl 目前只支持java语法,暂不支持kotlin。跟普通java接口差不多。
// IMyServer.aidl
package com.wen.aidlserver;
// Declare any non-default types here with import statements
interface IMyServer {
// 接收一个字符串参数
void setData(String value);
// 返回一个字符串
String getData();
}
在 java 包名下我们新增一个 services 包路径。新增一个 MyService.kt
在清单文件中记得加上,因为要给到外部应用使用,所以 exported= true。
<service
android:name=".services.MyService"
android:enabled="true"
android:exported="true" />
MyService 中的代码如下,此时服务端 APPService 项目的代码就完成了。这个APP我没写其他东西。运行起来也只是个demo。
package com.wen.aidlserver.services
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.wen.aidlserver.IMyServer
class MyService : Service() {
private val mBinder by lazy { MyBinder() }
override fun onBind(intent: Intent): IBinder {
return mBinder
}
private inner class MyBinder : IMyServer.Stub() {
private var mData: String = ""
override fun setData(value: String) {
mData = value
}
override fun getData() = mData
}
}
接下来新建一个 AIDLClient 项目 用作客户端。
在 AIDLClient 项目中,我们需要把 AIDLServer 中 aidl 生成的java文件(图7)拷贝到项目中(图8)来,注意包名也要一致。
此时就可以通过拷贝过来的 IMyServer 去连接 AIDLServer 项目了。如下
private inner class MyServiceConnection : ServiceConnection {
var mServer: IMyServer? = null
private set
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mServer = IMyServer.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
mServer = null
}
}
在 AIDLClient 项目中 通过以下代码绑定 AIDLServer 项目中定义的服务。
private fun setConnection() {
try {
val intent = Intent()
// AIDLServer 项目的包名(applicationId)
// AIDLServer 项目中注册的服务的全路径
intent.component =
ComponentName("com.wen.aidlserver", "com.wen.aidlserver.services.MyService")
bindService(intent, mMyServiceConnection, Context.BIND_AUTO_CREATE)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
当然解绑也是必不可少的。
override fun onDestroy() {
super.onDestroy()
unbindService(mMyServiceConnection)
}
在 Android11 版本以上 bindService 可能会返回 false。我们需要在 AIDLClient 中的清单文件(<manifest>下面)加上以下代码:
<queries>
<!--如果想要与其他的应用进行AIDL通信的话,需要在这里注册对应包名的信息-->
<package android:name="com.wen.aidlserver" />
</queries>
至此,两个APP之间就已经可以进行通讯了。在 AIDLClient 项目中,我简单的写了两个点击事件。一个用于设置数据到 AIDLServer 服务端。一个从服务端获取数据:
private var mNumber = 0
private fun initView() {
mViewBinding.tvGetData.setOnClickListener {
Log.d("MyLog ======>", "getData: ${mMyServiceConnection.mServer?.data}")
}
mViewBinding.tvSetData.setOnClickListener {
mNumber++
mMyServiceConnection.mServer?.data = mNumber.toString()
Log.d("MyLog ======>", "setData: $mNumber")
}
}
此时点击对应的按钮,我们可以把 mNumber 自增后的数据设置到服务端,然后又从服务端拿回数据。
MyLog ======> com.wen.aidlclient D setData: 1
MyLog ======> com.wen.aidlclient D getData: 1
MyLog ======> com.wen.aidlclient D setData: 2
MyLog ======> com.wen.aidlclient D getData: 2
MyLog ======> com.wen.aidlclient D setData: 3
MyLog ======> com.wen.aidlclient D getData: 3
MyLog ======> com.wen.aidlclient D setData: 4
MyLog ======> com.wen.aidlclient D setData: 5
MyLog ======> com.wen.aidlclient D getData: 5
接下来,如果你想往服务端传自定义对象的话。需要在服务端增加一个实现了 Parcelable接口的对象(只支持Parcelable,不支持Serializable)。(定义在main/java代码中而不是在main/aidl)如下我定义了个 User 类:
package com.wen.aidlserver.model
import android.os.Parcel
import android.os.Parcelable
class User(var name: String?, var age: Int?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readValue(Int::class.java.classLoader) as? Int
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeValue(age)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<User> {
override fun createFromParcel(parcel: Parcel): User {
return User(parcel)
}
override fun newArray(size: Int): Array<User?> {
return arrayOfNulls(size)
}
}
override fun toString(): String {
return "name = $name , age = $age"
}
}
然后根据User我们还需要在main/aidl文件夹下生成对应的aidl文件,注意包名要一致如(图9)
// User.aidl
package com.wen.aidlserver.model;
parcelable User;
IMyServer.aidl 增加两个方法。注意导包和参数前面加 in、out、inout。
// IMyServer.aidl
package com.wen.aidlserver;
// 自定义对象需要单独导包
import com.wen.aidlserver.model.User;
interface IMyServer {
// 接收一个字符串参数
void setData(String value);
// 返回一个字符串
String getData();
// 接收一个 User 对象
// 跟 java 参数不同的是前面加了个 in。有三个标识:in、out、inout。in:服务端可以接收参数,不可修改。out:服务端不可接收参数,可以修改。
// inout:服务端可接收参数,也可修改。
void setUser(in User user);
// 返回一个 User 对象
User getUser();
}
MyBinder 增加新增的两个方法实现:
private inner class MyBinder : IMyServer.Stub() {
private var mData: String? = ""
private var mUser: User? = null
override fun setData(value: String?) {
mData = value
}
override fun getData() = mData
override fun setUser(user: User?) {
mUser = user
}
override fun getUser() = mUser
}
运行项目,重新把 aidl 生成的 Java 文件同步到客户端。同时把 User 文件也拷贝到客户端。注意包名要一致!
稍微改下客户端的代码测试下新增的接口:
private fun initView() {
mViewBinding.tvGetData.setOnClickListener {
Log.d("MyLog ======>", "getData: ${mMyServiceConnection.mServer?.user?.toString()}")
}
mViewBinding.tvSetData.setOnClickListener {
val user = User("张三", 28)
mMyServiceConnection.mServer?.user = user
Log.d("MyLog ======>", "setData: ${user.toString()}")
}
}
执行结果如下:
MyLog ======> com.wen.aidlclient D getData: null
MyLog ======> com.wen.aidlclient D setData: name = 张三 , age = 28
MyLog ======> com.wen.aidlclient D getData: name = 张三 , age = 28
至此。服务端与客户端可实现自定义数据传输了。
当然 aidl 也是可以传递接口回调的。接下来我们简单定义一个 IMyServerlistener.aidl 文件。
// IMyServerlistener.aidl
package com.wen.aidlserver;
interface IMyServerlistener {
void onCallback(String str);
}
然后在 IMyServer.aidl 中增加一个 setListener方法。记得导包(import com.wen.aidlserver.IMyServerlistener;)。如下:
// IMyServer.aidl
package com.wen.aidlserver;
// 自定义对象需要单独导包
import com.wen.aidlserver.model.User;
import com.wen.aidlserver.IMyServerlistener;
interface IMyServer {
// 接收一个字符串参数
void setData(String value);
// 返回一个字符串
String getData();
// 接收一个 User 对象
// 跟 java 参数不同的是前面加了个 in。有三个标识:in、out、inout。in:服务端可以接收参数,不可修改。out:服务端不可接收参数,可以修改。
// inout:服务端可接收参数,也可修改。
void setUser(in User user);
// 返回一个 User 对象
User getUser();
// 接收一个 aidl 接口
void setListener(in IMyServerlistener listener);
}
在 MyBinder 中增加 setListener 的实现。为了方便演示,我直接回调了个字符串。
private inner class MyBinder : IMyServer.Stub() {
private var mData: String? = ""
private var mUser: User? = null
override fun setData(value: String?) {
mData = value
}
override fun getData() = mData
override fun setUser(user: User?) {
mUser = user
}
override fun getUser() = mUser
override fun setListener(listener: IMyServerlistener?) {
listener?.onCallback("MyService callback")
}
}
编译项目,再次把(build/generated)下 aidl 生成的java文件拷贝同步到客户端。
在客户端界面中增加一个按钮点击事件:
mViewBinding.tvSetListener.setOnClickListener {
mMyServiceConnection.mServer?.setListener(object : IMyServerlistener.Stub() {
override fun onCallback(str: String?) {
Log.d("MyLog ======>", "onCallback: $str")
}
})
}
客户端中点击结果:
MyLog ======> com.wen.aidlclient D onCallback: MyService callback
至此,一个简单的AIDL跨APP进程双向通讯已经完成了。