Android Aidl跨进程通讯(四)--接口回调,服务端向客户端发送数据

news2024/12/26 0:08:25

学更好的别人,

做更好的自己。

——《微卡智享》

6659c49042d964837f3a56d16073fb65.jpeg

本文长度为3325,预计阅读9分钟

前言

前几篇介绍了AIDL通讯的基础,进阶和异常捕获,本篇就来看看服务端怎么向客户端来实现发送消息。

实现服务端往客户端发送消息,主要还是通过接口回调的方式来实现,服务端主要通过RemoteCallbackList注册及解绑监听。

fffc97c64996a3caf38dd5b0d3fb9281.png

实现效果

c617e0e31946b3435d542ddf29bed93f.gif

7747157784f5d78ee4f5afe1405d8e12.png

接口回调实现

8935d949d0ee2d27811339073bd40ac4.png

微卡智享

#实现步骤
1服务端创建接口回调的AIDL
2通过RemoteCallbackList注册客户端的监听
3客户端拷贝创建的AIDL
4客户端写回调实现,注册到服务端

还是使用上几篇延用下来的Demo

服务端实现

7e23c4ef1897018470ce73efa98c4a1b.png

在服务端创建一个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);
}

c106dac96a859befcecd70d99bb4eb21.png

然后在原来的ITestDataAidlInterface.Aidl中首先要引入刚刚创建的IServiceListener

86daa7f231775a411c651f331645d670.png

接着在下面加入两个方法,一个注册监听,一个解绑监听,这两个方法前面加上了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中加上这两个方法的实现。

8e8b23e935b518207f5d86bd1a914f7c.png

在AidlService中定义一个RemoteCallbackList

83f8c66d2786d0c7940bdd4f52cc42a2.png

注册和解绑里面直接通过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

a5572a89695f795d8ce4a6362c966335.png

然后写了一个sendmsg的方法,这里用到的协程,每3秒服务端发送一次消息。

4ad602ac6a3dd268954e8fa1ad7061a5.png

在OnCreate中直接加入发送数据的调用

2dcfb375053a0803d9a37a42fb80da27.png

服务的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()
    }
}

客户端实现

067107c6c8d11be032fbd5d5d7eecefa.png

客户端首先也要将服务端已经写好的两个aidl文件拷贝过来

2c8bab243cc56472579d1e20da863470.png

然后在客户端MainActivity中定义IServiceListener.Stub的实现,这里是收到了消息后直接用Snake弹窗显示出来。

189d91c27fa5c4d6206de131667a0ec6.png

当bindService成功后,我们就直接调用注册监听,这里的协程加入了延时1秒,主要是服务端在onBind开启服务的时候有个时间过程,如果不加入延时直接注册,有可能服务端的Service还没启动起来,所以注册不上。

42d6796fd3b7d210700c2a0acb2f6a9e.png

onDestory中加入解绑回调,这样我们的MainActivity中关闭后,服务端的RemoteCallbackList也会解绑不再发送数据。


这样我们就可以实现服务端直接向客户端发送数据了,Demo源码中也已经更新上传了。

源码地址

https://github.com/Vaccae/AndroidAIDLDemo.git

点击原文链接可以看到“码云”的源码地址

2b03a72188de1a58e73a37009940bf27.png

4b1d31af76d4f8f269969ff129e4e5c5.png

往期精彩回顾

 

11bccb7218a90f193b1b6a0561e795a2.jpeg

Android Aidl跨进程通讯(三)--进阶使用

 

 

128d0162715478aee2e5472e40371692.jpeg

Android Aidl跨进程通讯(二)--异常捕获处理

 

 

183a2b887fa844642a59bb52f840f0ec.jpeg

Android Aidl跨进程通讯的简单使用

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1005901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.2.3 【MySQL】InnoDB的B+树索引的注意事项

6.2.3.1 根页面万年不动窝 B 树的形成过程是这样的&#xff1a; 每当为某个表创建一个 B 树索引&#xff08;聚簇索引不是人为创建的&#xff0c;默认就有&#xff09;的时候&#xff0c;都会为这个索引创建一个 根节点 页面。最开始表中没有数据的时候&#xff0c;每个 B 树…

S/4 FI之FBL3N/FBL3H/FAGLL03/FAGLL03H的区别

SAP 系统中&#xff0c;为了显示财务凭证行项目&#xff0c;由于不同的时间开发的功能&#xff0c;但实际在使用的过程&#xff0c;到底有些什么样区别&#xff1f; 本文档就是想对这一个问题做一个整体上的说明。 FBL3N&#xff0c;就是传统的行项目报表&#xff0c;在最早的…

刷刷刷——双指针算法

双指针算法 这里的双指针&#xff0c;可能并不是真正意义上的指针&#xff0c;而是模拟指针移动的过程。 常见的有两种&#xff1a; 双指针对撞&#xff1a; 即在顺序结构中&#xff0c;指针从两端向中间移动&#xff0c;然后逐渐逼近 终止条件一般是&#xff1a; left ri…

MATLAB中ischange函数用法

目录 语法 说明 示例 均值的变化 线性区的变化 矩阵数据 ischange函数的功能是查找数据中的突然变化。 语法 TF ischange(A) TF ischange(A,method) TF ischange(___,dim) TF ischange(___,Name,Value) [TF,S1] ischange(___) [TF,S1,S2] ischange(___) 说明 ​…

Python实现机器学习(下)— 数据预处理、模型训练和模型评估

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本门课程将介绍人工智能相关概念&#xff0c;重点讲解机器学习原理机器基本算法&#xff08;监督学习及非监督学习&#xff09;。使用python&#xff0c;结合sklearn、Pycharm进行编程&#xff0c;介绍iris&#xff08;鸢尾…

windows10搭建RocketMq

windows10搭建RocketMq 文章目录 windows10搭建RocketMq1.下载二进制RocketMq2.配置环境变量3.启动4.RocketMq控制台安装 1.下载二进制RocketMq 下载链接 2.配置环境变量 变量名:ROCKETMQ_HOME变量值:MQ解压路径 修改runbroker.cmd和runserver.cmd文件 把%CLASSPATH%用引…

SpringBoot + Prometheus + Grafana 打造可视化监控

SpringBoot Prometheus Grafana 打造可视化监控 文章目录 SpringBoot Prometheus Grafana 打造可视化监控常见的监控组件搭配安装Prometheus安装Grafana搭建SpringBoot项目引入依赖示例:监控SpringBoot内置Tomcat线程池的情况grafana创建监控看板 后台SpringBoot服务添加自…

【深度学习】 Python 和 NumPy 系列教程(十):NumPy详解:2、数组操作(索引和切片、形状操作、转置操作、拼接操作)

目录 一、前言 二、实验环境 三、NumPy 0、多维数组对象&#xff08;ndarray&#xff09; 1. 多维数组的属性 1、创建数组 2、数组操作 1. 索引和切片 a. 索引 b. 切片 2. 形状操作 a. 获取数组形状 b. 改变数组形状 c. 展平数组 3. 转置操作 a. 使用.T属性 b…

Redis模块四:常见的数据类型和使用

目录 Redis 的 5 大基础数据类型 ①字符串类型(String) ②字典类型(Hash) ③列表类型(List) ④集合类型(Set) ⑤有序集合类型(ZSet) Redis 的 5 大基础数据类型 String——字符串类型 Hash——字典类型 List——列表类型 Set——集合类型 ZSet——有序集合类型 …

后发而先至的腾讯混元大模型,到底有哪些技术亮点?

2023年的夏天已经结束了&#xff0c;但是&#xff0c;围绕AIGC大模型的关注热度&#xff0c;却丝毫没有衰退的意思。 在过去的大半年里&#xff0c;我们亲眼见证了大模型浪潮的崛起&#xff0c;甚至可以说是疯狂。截止7月&#xff0c;国内的大模型数量&#xff0c;已经超过130个…

MySQL与ES数据同步之异步调用

文章目录 简述SpringBoot项目引入依赖配置文件项目结构实体类配置类RabbitMQ交换机队列声明&#xff0c;绑定配置类回调接口配置类 Mapper接口UserMapper接口UserEsMapper Controller类Service接口Service实现类监听类/消费者 简述 上一篇是同步调用&#xff0c;我们在中间加上…

【海思SS626 | 开发环境】VMware17安装Ubuntu 18.04.6

目录 一、下载 Ubuntu 18.04.6 LTS二、VMware17创建虚拟机三、安装Ubuntu18.04LTS四、安装其他软件五、总结 一、下载 Ubuntu 18.04.6 LTS 问题&#xff1a;为什么要下载 Ubuntu18.04.6 LTS 而不是使用最新的&#xff0c;或者其他Linux发行版&#xff1f; 答&#xff1a;在ss6…

Python 图形化界面基础篇:使用框架( Frame )组织界面

Python 图形化界面基础篇&#xff1a;使用框架&#xff08; Frame &#xff09;组织界面 引言什么是 Tkinter 框架&#xff08; Frame &#xff09;&#xff1f;步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建框架&#xff08; F…

如何做到安全上网

随着信息化的发展&#xff0c;企业日常办公越来越依赖互联网&#xff0c;而访问互联网过程中&#xff0c;会遇到各种各样不容忽视的风险&#xff0c;例如员工主动故意的数据泄漏&#xff0c;后台应用程序偷偷向外部发信息&#xff0c;木马间谍软件的外联&#xff0c;以及各种挖…

聚观早报 | 荣耀V Purse定档;哪吒S迎来最新OTA升级

【聚观365】9月13日消息 荣耀V Purse定档 哪吒S迎来最新OTA升级 宝马将向其英国工厂投资7.5亿美元 英伟达称霸AI芯片领域致初创公司融资难 甲骨文第一财季收入约125亿美元增长9% 荣耀V Purse定档 不久前&#xff0c;荣耀官方推出了全新的荣耀Magic V2内折叠屏旗舰&#x…

【Linux从入门到精通】信号(信号保存 信号的处理)

本篇文章接着信号&#xff08;初识信号 & 信号的产生&#xff09;进行讲解。学完信号的产生后&#xff0c;我们也了解了信号的一些结论。同时还留下了很多疑问&#xff1a; 上篇文章所说的所有信号产生&#xff0c;最终都要有OS来进行执行&#xff0c;为什么呢&#xff1f;…

在Android studio 创建Flutter项目运行出现问题总结

在Android studio 中配置Flutter出现的问题 A problem occurred configuring root project ‘android’出现这个问题。解决办法 首先找到flutter配置的位置 在D:\xxx\flutter\packages\flutter_tools\gradle位置中的flutter.gradle buildscript { repositories { googl…

相机坐标系 -> 像素坐标系

代码链接&#xff1a;https://github.com/PanJinquan/python-learning-notes/blob/master/modules/utils_3d/camera_tools.py def __cam2pixel(cam_coord, f, c):"""相机坐标系 -> 像素坐标系: (f / dx) * (X / Z) f * (X / Z) / dxcx,ppx260.166; cy,ppy…

分库分表---理论

目录 一、垂直切分 1、垂直分库 2、垂直分表 3、垂直切分优缺点 二、水平切分 1、水平分库 2、水平分表 3、水平切分优缺点 三、数据分片规则 1、Hash取模分表 2、数值Range分表 3、一致性Hash算法 四、分库分表带来的问题 1、分布式事务问题 2、跨节点关联查询…

【FAQ】本地录像视频文件如何推送到视频监控平台EasyCVR进行AI视频智能分析?

安防监控平台EasyCVR支持多协议、多类型设备接入&#xff0c;可以实现多现场的前端摄像头等设备统一集中接入与视频汇聚管理&#xff0c;并能进行视频高清监控、录像、云存储与磁盘阵列存储、检索与回放、级联共享等视频功能。视频汇聚平台既具备传统安防监控、视频监控的视频能…