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

学更好的别人,

做更好的自己。

——《微卡智享》

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 statementsinterface 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.aidlserviceimport 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.Throwsclass AidlService : Service() {val CHANNEL_STRING = "vac.test.aidlservice"val CHANNEL_ID = 0x11val 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 falseitem?.let { t ->t.code = it.codet.name = it.namet.price = it.pricet.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.classLoaderval price = it.getFloat("price")val qty = it.getInt("qty")mTestDatas.map { t ->t.price = pricet.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 NotificationManagerval 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.rhkb.cn/news/131040.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv(python)视频按帧切片/cv2.VideoCapture()用法

一、介绍 cv2.VideoCapture是OpenCV中一个用于捕捉视频的类。它可以访问计算机的摄像头&#xff0c;或从视频文件中读取图像。通过cv2.VideoCapture&#xff0c;用户可以轻松地捕捉、保存、编辑和传输视频流数据。 使用cv2.VideoCapture可以实现以下功能&#xff1a; 1. 打开…

Spring-Cloud GateWay+Vue 跨域方案汇总

文章目录 一、简介背景和概述 二、前端跨域解决方案Axios跨域CORS跨域 三、后端跨域解决方案反向代理服务器 四、Spring Cloud中的跨域解决方案Gateway网关的跨域配置 五、基于Vue和Spring Cloud的跨域整合实践**这两种配置只需配置一种即可生效&#xff08;前端or后端&#xf…

原型链解释

一、什么是原型链 原型链是javascript中用来实现类似类继承的一套机制。像链条一样把javascript中的对象连接起来&#xff0c;实现类似子联系父的现象。 二、原型链的实现 总的来说&#xff0c;就是&#xff1a; 对象的__proto__指向其构造器的prototype对象&#xff0c;然后…

分布式id的概述与实现

文章目录 前言一、分布式id技术选型二、雪花算法三、在项目中集成雪花算法 前言 随着业务的增长&#xff0c;数据表可能要占用很大的物理存储空间&#xff0c;为了解决该问题&#xff0c;后期使用数据库分片技术。将一个数据库进行拆分&#xff0c;通过数据库中间件连接。如果…

postgresql -数据库事务与并发控制

postgresql -数据库事务与并发控制 数据库事务事务控制语句并发与隔离数据库事务 事务控制语句 -- serial 自增 CREATE TABLE accounts(id serial PRIMARY KEY,user_name varchar(50),balance numeric

[php] 文件上传的一个项目emmm

项目完整地址 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>上传文件</title><link href"./css/bootstrap.min.css" rel"stylesheet"><style>font-face {fo…

广州xx策划公司MongoDB恢复-2023.09.09

2023.09.08用户的MongoDB数据库被勒索病毒攻击&#xff0c;数据全部被清空。 提示&#xff1a; mongoDB的默认端口为27017&#xff0c;黑客通常通过全网段扫描27017是否开放判断是否是MongoDB服务器。一旦发现27017开放&#xff0c;黑客就会用空密码、弱密码尝试连接数据库。黑…

经典排序算法总结

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

深入理解JVM虚拟机第四篇:一些常用的JVM虚拟机

一&#xff1a;Sun Classic VM虚拟机 早在1996年Java1.0版本的时候&#xff0c;Sun公司发布了一款名为Sun classic VM的Java虚拟机&#xff0c;它同时也是世界上第一款商用Java虚拟机&#xff0c;JDK1.4时完全被淘汰。 现在hotspot内置了此虚拟机。 这款虚拟机内部只提供解释器…

银行笔试篇---职业能力测试(行测)

数字推理 数字推理可分为等差数列、等比数列、和数列、积数列、幂数列以及分数数列六类&#xff0c;做题时的总体原则为&#xff1a; 关键点1&#xff1a;凡是一次变化找不到规律的&#xff0c;直接放弃&#xff01;所谓一次变化指的是&#xff1a;1.通过一次相邻两数作差、作…

【数据库】Navicate运行数据区sql文件 1046 no database selected

文章目录 前言一、现象二、解决 前言 要通过Navicat导入数据库文件&#xff0c;但是不成功报错1046 no database selected 一、现象 选中已经建立的连接&#xff0c;右键运行sql文件&#xff0c;报错 二、解决 1、先在建立的localhost中右键建立和要导入数据库同名的数据…

C++信息学奥赛1170:计算2的N次方

#include <iostream> #include <string> #include <cstring>using namespace std;int main() {int n;cin >> n; // 输入一个整数nint arr[100];memset(arr, -1, sizeof(arr)); // 将数组arr的元素初始化为-1&#xff0c;sizeof(arr)表示arr数组的字节…

【STM32】影子寄存器

不可操作但是真正起作用的寄存器是影子寄存器 定时器框图中&#xff0c;有些寄存器下有个阴影 这些阴影的表示这些寄存器存在影子寄存器。 图中也有对这些影子的说明&#xff0c;在U事件时传送预装载寄存器至实际寄存器。 有阴影的寄存器(AutoReloadRegister)&#xff0c;表…

数据库管理软件NoSQLBooster for MongoDB 8.1 Mac

NoSQLBooster for MongoDB 是一款功能强大的 MongoDB 数据库管理工具。它提供了一个直观的用户界面&#xff0c;使用户能够轻松地浏览、查询和修改 MongoDB 数据库中的数据。 NoSQLBooster for MongoDB 支持多种查询方式&#xff0c;包括基本查询、聚合管道、地理空间查询等。它…

Python解析MDX词典数据并保存到Excel

原始数据和处理结果&#xff1a; https://gitcode.net/as604049322/blog_data/-/tree/master/mdx 下载help.mdx词典后&#xff0c;我们无法直接查看&#xff0c;我们可以使用readmdict库来完成对mdx文件的读取。 安装库&#xff1a; pip install readmdict对于Windows平台还…

【建站教程】使用阿里云服务器怎么搭建网站?

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;阿小云来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#x…

绝热量热法反应热测试过程中的温度和压力自动跟踪控制解决方案

摘要&#xff1a;现有的ARC加速量热仪普遍存在单热电偶温差测量误差大造成绝热效果不好&#xff0c;以及样品球较大壁厚造成热惰性因子较大&#xff0c;都使得ARC测量精度不高。为此本文提出了技术改进解决方案&#xff0c;一是采用多只热电偶组成的温差热电堆进行温差测量&…

ChatGPT提示词(prompt)资源汇总

文章目录 awesome-chatgpt-promptsLearn PromptingSnack PromptFlow GPTPrompt VineChatGPT 指令大全AI Toolbox HubAI Short ChatGPT是一种强大的生成式AI模型&#xff0c;而提示词&#xff08;prompt&#xff09;则是与ChatGPT一起使用的指导性文本&#xff0c;用于引导模型生…

R语言并行计算提高速度丨parallel包和foreach包

并行计算提高R语言速度 今天与大家分享的是R语言中的并行计算的内容&#xff0c;将探讨如何使用parallel和foreach包在R中进行并行计算&#xff0c;以及在不同情况下提高计算效率的方法。 目标&#xff1a;让计算等待时间缩短&#xff01; 1. 什么是并行计算&#xff1f; 并行计…

《Linkerd 2.0:下一代服务网格的探索》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…