背景
在公司实习的过程中需要用到跨进程的通信,查看项目之前的代码发现是通过aidl绑定的两个进程,但是因为两个进程需要互相收发消息,所以A→B, B←A进行了两次绑定。本着能只绑定一次就不多浪费一次的原则,选择对这个通信方式进行改造。
首先aidl这个东西就不再多赘述,是非常基础的多进程通信方法。
其次通过一次aidl绑定后就可以调用Server端的服务,这一点也不多赘述,因为是aidl的平A用法。
这里主要说明一下如何使用aidl实现反向调用客户端的方法
用法
通过查找其他博主的文章,发现统一使用了RemoteCallbackList<?>()
这种列表,通过客户端将callback注册到这个列表中,在服务端遍历这个列表,便可以调用里面存储的客户端的方法。
下面是具体做法:
-
首先书写两个aidl文件,作为服务端和客户端双方希望对方调用的接口:
//这是服务端提供给客户端调用的接口 // Myinterface.aidl package com.example.crossprocesslistenertest; import com.example.crossprocesslistenertest.Anotherlistener; interface Myinterface { //服务端提供的服务 void doService(int id, out Bundle params); //服务端提供给客户端注册回调的方法 void registerListener(Anotherlistener listener); //服务端提供给客户端解注册回调的方法 void unregisterListener(Anotherlistener listener); }
//客户端提供给服务端回调的方法 // Anotherlistener.aidl package com.example.crossprocesslistenertest; interface Anotherlistener { void doListener1(int id, inout Bundle params); }
别忘了build一下,否则接口文件不会生成
-
创建一个Service,作为被绑定的服务端(别忘了在AndroidMaifest中把这个Service注册在另一个进程)
//AnotherService,表示在另一个进程 class AnotherService : Service() { private val TAG = "remote process" private val mListener = RemoteCallbackList<Anotherlistener>() //在这里实现一下Service提供的服务接口 private val service = object : Myinterface.Stub(){ //这个接口方法用日志展示一下这个服务被调用 override fun doService(id : Int, params : Bundle) { Log.d(TAG,"the remote service was did") } //这个接口方法用来注册客户端的回调方法 override fun registerListener(listener: Anotherlistener?) { //这里调用RemoteCallbackList的register方法,可以将泛型方法注册进去 mListener.register(listener) } //这个接口方法用来解注册客户端的回调方法 override fun unregisterListener(listener: Anotherlistener?) { mListener.unregister(listener) } } //通过这个方法调用主进程中提供的回调方法,主要是通过遍历list中注册的方法 fun doMainListener(){ //首先要开启一下“广播”,注意这里并不是四大组件中的广播,这个方法会返回列表中的object数量 val n =mListener.beginBroadcast() //我们可以通过这个日志打印一下具体有几个回调,一般只有一个 Log.d("AnotherService","${mListener.registeredCallbackCount}") //遍历列表中的回调方法 for (i in 0 until n){ //调用getBroadcastItem(),通过具体的索引可以获得这个回调对象,注意这个对象类型是我们提供的泛型,也就是说这就是我们要拿到的回调接口!这是一件好事,后面会提到 mListener.getBroadcastItem(i).doListener1() //因为我们已经通过泛型定义了存储在列表中的的回调接口,我们就可以直接使用它们了(注意这里的doListener是我们在客户端aidl中提供的) } //调用完成之后,一定要记得调用dinishBBroadcast方法关闭广播 mListener.finishBroadcast() } //这里就是正常的绑定流程,将service接口返回即可 override fun onBind(intent: Intent): IBinder? { return service.asBinder } }
-
接下来在主进程中的MainActivity中书写逻辑:
class MainActivity : AppCompatActivity() { //在这里准备一个service属性,用来获取得到的服务端接口 private var mService : Myinterface? = null private val TAG = "main process" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //在onCreate方法中对服务端服务进行绑定 bind() //通过给按钮注册点击事件调用服务端提供的服务 do_service.setOnClickListener { mService?.doService(3, Bundle().apply { putString("key","11111") }) } } //实现客户端的aidl接口,也就是希望服务端回调的方法 private val listener = object : Anotherlistener.Stub(){ override fun doListener1(id : Int, params : Bundle) { //同样通过日志表明这个方法是否有被调用 Log.d(TAG, "the main process listener1111 was called") } } //bind service必备的connection属性 private val connection = object : ServiceConnection{ //在和服务建立连接的回调中,注册自己的回调(上面的listener) override fun onServiceConnected(name: ComponentName?, service: IBinder?) { //首先拿到service端的服务接口 mService = Myinterface.Stub.asInterface(service) //调用服务端的注册listener方法,并将自己的接口回调注册进去 mService?.registerListener(listener) Log.d(TAG, "Connection success!") } override fun onServiceDisconnected(name: ComponentName?) { mService = null } } //绑定服务端的方法 private fun bind(){ bindService(Intent(this, AnotherService::class.java).setType("MAIN"), connection, BIND_AUTO_CREATE) } //解绑方法 private fun unbind(){ if(mService?.asBinder()?.isBinderAlive == true){ mService?.unregisterListener(listener) unbindService(connection) } } //这里不再赘述 override fun onDestroy() { if (mService?.asBinder()?.isBinderAlive == true){ //从list中解注册,但实际上好像不需要,在连接断开后,列表中的回调会自动清除 mService?.unregisterListener(listener) } unbindService(connection) super.onDestroy() } }
-
尝试一下客户端使用Web端的服务:(每次点击按钮都会打印)
-
目前服务端是一个Service,没有View,只能添加一个线程帮我们去触发这个操作:
//在AnotherService中添加: override fun onCreate() { super.onCreate() Thread(doSth).start() } //这个方法会创建一个Runnable,我们通过lambda重写里面的run方法 private val doSth = Runnable { //每十秒调用一次客户端的回调 for (i in 1 .. 10){ Thread.sleep(1000) doMainListener() } }
以上省略6次
以上实现了AIDL中Server端回调Client端的方法,但是如果每次调用callback的时候,都需要对
RemoteCallbackList
进行开启→遍历→关闭,是不是有点费事?如果我们的的回调接口中有很多个方法,而这些方法在服务端被异步调用,就会存在RemoteCallbackList的开启问题,因为RemoteCallbackList的所有方法是被Synchronize修饰的,那么异步调用就会被强制变成同步过程,降低了效率。
那么为了解决上述的问题,在下一篇文章中会尝试将RemoteCallbackList中的回调方法取出,并直接使用