一、大致流程
1.服务端创建 .aidl 文件 2.定义接口 3.build生成相应的java文件 4.向客户端公开接口(写一个service,重写onBind(),返回Stub类的实现) 5.复制aidl文件及相应的bean类(如果有的话)到客户端 6.客户端binderService拿到binder实例调用服务端方法。
例子: 1.服务端在src/main下创建aidl文件IPersonManager.aidl
package com.lmy.androidutilcode;
import com.lmy.androidutilcode.bean.Person;
interface IPersonManager {
List<Person> getPersonList();
boolean addPerson(inout Person person);
}
2.定义好要传输的对象Person
class Person(var name: String? = "") : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString())
override fun toString(): String {
return "Person(name=$name) hashcode = ${hashCode()}"
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
}
fun readFromParcel(parcel: Parcel) {
this.name = parcel.readString()
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}
override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
3.然后得在aidl的相同目录下也需要声明一下这个Person对象.新建一个Person.aidl
package com.xfhy.allinone.ipc.aidl;
parcelable Person;
如图:
4.rebuild一下,AS会自动生成如下代码IPersonManager.java。 注意:aidl文件中最好不要用中文注释
5.服务端像客户端公开接口 定义一个Service,然后将其process设置成一个新的进程,与主进程区分开(或者像我这里用2个module,即2个application)。模拟跨进程访问,它里面需要实现.aidl生成的接口
class RemoteService : Service() {
private val mPersonList = mutableListOf<Person?>()
private val mBinder: Binder = object : IPersonManager.Stub() {
override fun getPersonList(): MutableList<Person?> = mPersonList
override fun addPerson(person: Person?): Boolean {
return mPersonList.add(person)
}
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
override fun onCreate() {
super.onCreate()
mPersonList.add(Person("Garen"))
mPersonList.add(Person("Darius"))
}
}
实现的IPersonManager.Stub是一个Binder,需要通过onBind()返回,客户端需要通过这个Binder来跨进程调用Service这边的服务.
6.复制aidl文件和相应的bean对象到客户端 注意:目录结构要完全一样
7.客户端调用
class AidlActivity : TitleBarActivity() {
companion object {
const val TAG = "lmy"
}
private var remoteServer: IPersonManager? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
log(TAG, "onServiceConnected")
remoteServer = IPersonManager.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
log(TAG, "onServiceDisconnected")
}
}
override fun getThisTitle(): CharSequence {
return "AIDL"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_aidl)
btnConnect.setOnClickListener {
connectService()
}
btnGetPerson.setOnClickListener {
getPerson()
}
btnAddPerson.setOnClickListener {
addPerson()
}
}
private fun connectService() {
val intent = Intent()
intent.action = "可以给服务端的service定义一个action"
intent.setPackage("服务端包名")
val bindServiceResult = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
private fun addPerson() {
try {
val addPersonResult = remoteServer?.addPerson(Person("盖伦"))
log(TAG, "addPerson result = $addPersonResult")
} catch (e: RemoteException) {
e.printStackTrace()
} catch (e: DeadObjectException) {
e.printStackTrace()
} catch (e: SecurityException) {
e.printStackTrace()
}
}
private fun getPerson() {
val personList = remoteServer?.personList
log(TAG, "person 列表 $personList")
}
override fun onDestroy() {
super.onDestroy()
unbindService(serviceConnection)
}
}
注意: 如果targetSdk是30,那么需要处理Android 11中的程序包可见性 具体参见: https://developer.android.com/about/versions/11/privacy/package-visibility
二、in,out,inout关键字
在上面定义AIDL接口的时候,咱用到了一个关键字in,这个关键是其实是定向tag,是用来指出数据流通的方式。还有2个tag是out和inout,所有的非基本参数都需要一个定向tag来指出数据的流向,基本参数的定向tag默认并且只能是in。
in方式是可以从客户端向服务端传数据的,out则不行 out方式是可以从服务端向客户端传数据的,in则不行 不管服务端是否有修改传过去的对象数据,客户端的对象引用是不会变的,变化的只是客户端的数据,合情合理,跨进程是序列化与反序列化的方式操作数据。
三、oneway 关键字
将aidl接口的方法前加上oneway关键字则这个方法是异步调用,不会阻塞调用线程,当客户端这边调用服务端的方法时,如果不需要知道其返回结果,这时使用异步调用可以提高客户端的执行效率。
验证:我将aidl接口方法定义成oneway的,在服务端AIDL方法实现中加入Thread.sleep(2000)阻塞一下方法调用,然后客户端调用这个方法,查看方法调用的前后时间
private fun addPersonOneway() { log(TAG, “oneway开始时间: ${System.currentTimeMillis()}”) remoteServer?.addPersonOneway(Person(“oneway”)) log(TAG, “oneway结束时间: ${System.currentTimeMillis()}”) }
//日志输出 //oneway开始时间: 1608858291371 //oneway结束时间: 1608858291372 可以看到,客户端调用这个方法时确实是没有被阻塞的.
四、线程安全
AIDL的方法是在服务端的Binder线程池中执行的,所以多个客户端同时进行连接且操作数据时可能存在多个线程同时访问的情形.这样的话,我们就需要在服务端AIDL方法中处理多线程同步问题.
|