赞
踩
学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3325字,预计阅读9分钟
前言
前几篇介绍了AIDL通讯的基础,进阶和异常捕获,本篇就来看看服务端怎么向客户端来实现发送消息。
实现服务端往客户端发送消息,主要还是通过接口回调的方式来实现,服务端主要通过RemoteCallbackList注册及解绑监听。
实现效果
接口回调实现
微卡智享
# | 实现步骤 |
---|---|
1 | 服务端创建接口回调的AIDL |
2 | 通过RemoteCallbackList注册客户端的监听 |
3 | 客户端拷贝创建的AIDL |
4 | 客户端写回调实现,注册到服务端 |
还是使用上几篇延用下来的Demo
在服务端创建一个IServiceListener的AIDL文件,里面写一个方法为calback,参数是String类型
- // IServiceListener.aidl
- package vac.test.aidlservice;
-
-
- // Declare any non-default types here with import statements
-
-
- interface IServiceListener {
-
-
- void callback(String msg);
- }
然后在原来的ITestDataAidlInterface.Aidl中首先要引入刚刚创建的IServiceListener
接着在下面加入两个方法,一个注册监听,一个解绑监听,这两个方法前面加上了oneway的修饰,使IPC调用变成非阻塞的,oneway在上一篇中有简单介绍过。
- // ITestDataAidlInterface.aidl
- package vac.test.aidlservice;
-
-
- // Declare any non-default types here with import statements
- import vac.test.aidlservice.IServiceListener;
-
-
- interface ITestDataAidlInterface { Bundle bundle);
-
-
- //注册监听
- oneway void registerListener(IServiceListener listener);
-
-
- //解绑监听
- oneway void unregisterListener(IServiceListener listener);
- }
AIDL这样就算写好了,然后我们重新Rebuild一下项目后,需要在Service中加上这两个方法的实现。
在AidlService中定义一个RemoteCallbackList
注册和解绑里面直接通过RemoteCallbackList中的register和unregister实现。
RemoteCallbackList用于管理一组已注册的IInterface回调,并在它们的进程消失时自动从列表中清理它们。RemoteCallbackList通常用于执行从Service到其客户端的回调,实现跨进程通信。其特点主要是
通过调用IInterface.asBinder()方法,根据底层的唯一Binder来识别每个注册的接口。
给每个注册的接口附加了一个IBinder.DeathRecipient,这样如果接口所在的进程死亡了,它就可以从列表中清除掉。
对底层接口列表进行了加锁处理,以应对多线程的并发调用,同时提供了一种线程安全的方式来遍历列表的快照,而不需要持有锁。
使用RemoteCallbackList先创建一个实例,并调用它的register(E)和unregister(E)方法作为客户端注册和解绑。
要回调到注册的客户端使用beginBroadcast()、getBroadcastItem(int)和finishBroadcast()方法。在beginBroadcast()后必须要先执行finishBroadcast()后,才可以进行下次的beginBroadcast(),否则会报错beginBroadcast() called while already in a broadcast
然后写了一个sendmsg的方法,这里用到的协程,每3秒服务端发送一次消息。
在OnCreate中直接加入发送数据的调用
服务的onDestroy中要记得加入RemoteCallbackList的kill()。
- package vac.test.aidlservice
-
-
- import android.app.Notification
- import android.app.NotificationChannel
- import android.app.NotificationManager
- import android.app.Service
- import android.content.Context
- import android.content.Intent
- import android.os.Build
- import android.os.Bundle
- import android.os.IBinder
- import android.os.RemoteCallbackList
- import android.os.RemoteException
- import android.util.DisplayMetrics
- import android.util.Log
- import androidx.annotation.RequiresApi
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.GlobalScope
- import kotlinx.coroutines.delay
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.sync.Mutex
- import kotlinx.coroutines.sync.withLock
- import java.io.IOException
- import kotlin.jvm.Throws
-
-
- class AidlService : Service() {
-
-
- val CHANNEL_STRING = "vac.test.aidlservice"
- val CHANNEL_ID = 0x11
-
-
- val mTestDatas: MutableList<TestData> = mutableListOf()
-
-
- //监听集合 用于管理一组已注册的IInterface回调
- private val mCallBackList: RemoteCallbackList<IServiceListener> = RemoteCallbackList()
-
-
- private var testBinder = object : ITestDataAidlInterface.Stub() {
-
-
- override fun basicTypes(
- anInt: Int,
- aLong: Long,
- aBoolean: Boolean,
- aFloat: Float,
- aDouble: Double,
- aString: String?
- ) {
- TODO("Not yet implemented")
- }
-
-
- override fun getTestData(code: String?): TestData? {
- // return mTestDatas.firstOrNull { t -> t.code == code }
- throw SecurityException("我是AidlService进程中的异常,你看到了吗?")
- }
-
-
- override fun getTestDatas(): MutableList<TestData> {
- return mTestDatas
- }
-
-
- override fun updateTestData(data: TestData?): Boolean {
- data?.let {
- var item: TestData? =
- mTestDatas.firstOrNull { t -> t.code == it.code } ?: return false
- item?.let { t ->
- t.code = it.code
- t.name = it.name
- t.price = it.price
- t.qty = it.qty
- }
- return true
- } ?: return false
- }
-
-
- override fun updateTestDatsList(datas: MutableList<TestData>?): Boolean {
- datas?.let {
- val item = TestData("99999", "我是新加数据", 1.0f, 1)
- it.add(item)
-
-
- mTestDatas.addAll(it)
-
-
- it.clear()
- it.addAll(mTestDatas)
- return true
- } ?: return false
- }
-
-
- override fun transBundle(bundle: Bundle?): MutableList<TestData> {
- bundle?.let { it ->
- /*
- Android有两种不同的classloaders:framework classloader和apk classloader,
- 其中framework classloader知道怎么加载android classes,
- apk classloader继承自framework classloader,所以也知道怎么加载android classes。
- 但在应用刚启动时,默认class loader是apk classloader,在系统内存不足应用被系统回收会再次启动,
- 这个默认class loader会变为framework classloader了,所以对于自己的类会报ClassNotFoundException
- 就会出现android.os.BadParcelableException: ClassNotFoundException when unmarshalling
- */
- //所以在bundle数据读取前,先设置classloader后,才能正确的读取自定义类
- it.classLoader = TestData::class.java.classLoader
-
-
- val price = it.getFloat("price")
- val qty = it.getInt("qty")
-
-
- mTestDatas.map { t ->
- t.price = price
- t.qty = qty
- }
-
-
- val list = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- it.getParcelableArrayList("listdatas", TestData::class.java)
- } else {
- it.getParcelableArrayList<TestData>("listdatas")
- }
- list?.let { item ->
- mTestDatas.addAll(item)
- }
- }
- return mTestDatas
- }
-
-
- //注册监听
- override fun registerListener(listener: IServiceListener?) {
- mCallBackList.register(listener);
- }
-
-
- //解绑监听
- override fun unregisterListener(listener: IServiceListener?) {
- mCallBackList.unregister(listener);
- }
-
-
- }
-
-
- fun initList() {
- for (i in 1..5) {
- val price = ((0..10).random()).toFloat()
- val qty = (10..50).random()
- val item = TestData("0000${i}", "测试数据${i}", price, qty)
- mTestDatas.add(item)
- }
- }
-
-
- fun sendmsg() {
- GlobalScope.launch(Dispatchers.Default) {
- delay(3000)
- repeat(10) { i ->
- delay(3000)
- val mutex = Mutex()
- mutex.withLock {
- val size = mCallBackList.beginBroadcast()
- Log.i("aidlpkg", "mCallBackList:${size}")
- try {
- val msg = "服务端发的第${i}次消息"
- Log.i("aidlpkg", "sendMsgtoClient:${msg}")
- for (i in 0 until size) {
- mCallBackList.getBroadcastItem(i).callback(msg)
- }
- mCallBackList.finishBroadcast()
- } catch (e: IllegalStateException) {
- Log.e("aidlpkg", e.message.toString())
- mCallBackList.finishBroadcast()
- throw RemoteException(e.message)
- }
- }
- }
- }
- }
-
-
- fun startServiceForeground() {
- val notificationManager =
- getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val channel = NotificationChannel(
- CHANNEL_STRING, "AidlServer",
- NotificationManager.IMPORTANCE_LOW
- )
- notificationManager.createNotificationChannel(channel)
- val notification = Notification.Builder(applicationContext, CHANNEL_STRING).build()
- startForeground(CHANNEL_ID, notification)
- }
-
-
- override fun onCreate() {
- super.onCreate()
- /*Debug版本时调试使用 */
- // Debug.waitForDebugger()
- startServiceForeground()
- //初始化数据
- initList()
-
-
- //开始发送返回消息
- sendmsg()
- }
-
-
- override fun onBind(intent: Intent): IBinder {
- return testBinder
- }
-
-
- override fun onDestroy() {
- super.onDestroy()
- mCallBackList.kill()
- }
- }
客户端首先也要将服务端已经写好的两个aidl文件拷贝过来
然后在客户端MainActivity中定义IServiceListener.Stub的实现,这里是收到了消息后直接用Snake弹窗显示出来。
当bindService成功后,我们就直接调用注册监听,这里的协程加入了延时1秒,主要是服务端在onBind开启服务的时候有个时间过程,如果不加入延时直接注册,有可能服务端的Service还没启动起来,所以注册不上。
onDestory中加入解绑回调,这样我们的MainActivity中关闭后,服务端的RemoteCallbackList也会解绑不再发送数据。
这样我们就可以实现服务端直接向客户端发送数据了,Demo源码中也已经更新上传了。
源码地址
https://github.com/Vaccae/AndroidAIDLDemo.git
点击原文链接可以看到“码云”的源码地址
完
往期精彩回顾
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。