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.mzph.cn/news/82804.shtml

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

相关文章

java版Spring Cloud+Mybatis+Oauth2+分布式+微服务+实现工程管理系统

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

爬虫框架Scrapy学习笔记-1

前言 在现代互联网时代&#xff0c;网页数据获取和处理已经成为了重要的技能之一。无论是为了获取信息、做市场研究&#xff0c;还是进行数据分析&#xff0c;掌握网页爬取和数据处理技术都是非常有用的。本文将介绍从网页加载到数据存储的完整过程&#xff0c;包括网络请求、…

(手撕)数据结构--->堆

文章内容 目录 一&#xff1a;堆的相关概念与结构 二&#xff1a;堆的代码实现与重要接口代码讲解 让我们一起来学习:一种特殊的数据结构吧&#xff01;&#xff01;&#xff01;&#xff01; 一&#xff1a;堆的相关概念与结构 在前面我们已经简单的学习过了二叉树的链式存储结…

剑指YOLOv5改进主干RepViT系列: 最新重参数化结构|ICCV 2023 最新开源移动端网络架构 RepViT,1.3ms 延迟,速度贼快

💡本篇内容:剑指YOLOv5改进主干RepViT系列: 最新重参数化结构|ICCV 2023 最新开源移动端网络架构 RepViT,1.3ms 延迟,速度贼快 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv5 按步骤操作运行改进后的代码即可 💡:重点:该专栏《剑指YOLOv5原创改进》只更新…

SOA、分布式、微服务

SOA&#xff1a; SOA是一种软件设计架构&#xff0c;用于构建分布式系统和应用程序。它将应用程序拆分为一系列松耦合的服务&#xff0c;这些服务通过标准化的接口进行通信&#xff0c;并能够以可编程方式组合和重用。SOA的目标是提高系统的灵活性、可扩展性和可维护性。 特点&…

mac电脑部署安装powershell

部署安装powershell 要在mac部署安装powershell&#xff0c;可以使用homebrew来进行部署安装&#xff0c;故其步骤如下&#xff1a; 安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 利用brew部署…

项目管理-甲方的心累

做了一年开发、四年的项目管理&#xff0c;近期公司接了一个第三方的平台&#xff0c;需要做私有化部署&#xff0c;负责的同事修婚假了&#xff0c;我替了2周&#xff0c;然后就好累好累&#xff0c;感觉需要自己亲力亲为去做每个项目经理要做的事情&#xff0c;但是又因为是不…

Linux Day17 生产者消费者

一、生产者消费者问题概述 生产者 / 消费者问题&#xff0c;也被称作有限缓冲问题。两个或者更多的线程共享同一个缓冲 区&#xff0c;其中一个或多个线程作为 “ 生产者 ” 会不断地向缓冲区中添加数据&#xff0c;另一个或者多个线程作为 “ 消费者 ” 从缓冲区中取走数据。…

【MySQL系列】- MySQL自动备份详解

【MySQL系列】- MySQL自动备份详解 文章目录 【MySQL系列】- MySQL自动备份详解一、需求背景二、Windows mysql自动备份方法2.1 复制date文件夹备份实验备份环境创建bat直接备份脚本 2 .2 mysqldump备份成sql文件创建mysqldump备份脚本 2 .3 利用WinRAR对MySQL数据库进行定时备…

Android 白天黑夜模式设置

白天黑夜模式是一种动态的UI模式,根据当前时间或用户设置的偏好,在白天和黑夜之间进行切换。它通过调整应用程序的颜色、亮度和其他可视化元素来提供更加舒适和易读的用户界面。 一、简单设置 UiModeManager 是用于管理和控制用户界面模式(UI Mode)。它提供了一组方法,允…

【每日一题】154. 寻找旋转排序数组中的最小值 II

154. 寻找旋转排序数组中的最小值 II - 力扣&#xff08;LeetCode&#xff09; 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,4,4,5,6,7] 在变化后可能得到&#xff1…

CListCtrl控件为只显示一列,持滚动显示其他,不用SetScrollFlags

CListCtrl控件为只显示一列,持滚动显示其他,不用SetScrollFlags 2023/9/5 下午4:52:58 如果您不希望使用 SetScrollFlags 函数来设置滚动条样式,可以使用以下代码将 CListCtrl 控件设置为只显示一列,并支持滚动显示其他内容: cpp // 设置控件样式和属性 m_listCtrl.Se…

基于SSM的智慧城市实验室主页系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

linux学习实操计划0103-安装软件

本系列内容全部给基于Ubuntu操作系统。 系统版本&#xff1a;#32~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 18 10:40:13 UTC 1 安装deb格式软件 Debian包是Unixar的标准归档&#xff0c;将包文件信息以及包内容&#xff0c;经过gzip和tar打包而成。 处理这些包的经典程序是…

git基本手册

Git and GitHub for Beginners Tutorial - YouTube Kevin Stratvert git config --global user.name “xxx” git config --global user.email xxxxx.com 设置默认分支 git config --global init.default branch main git config -h查看帮助 详细帮助 git help config 清除 cl…

vue国际化教程

需求背景 项目需求要做国际化&#xff0c;结果网上找了好几篇文章&#xff0c;没有一个可以一次性搞定&#xff0c;现在这里总结一下。首先&#xff0c;我们分为两部分处理&#xff0c;一个是前端页面的静态文字&#xff0c;这个由前端vue.json自行处理。第二部分就是后端的错…

剑指YOLOv7改进最新重参数化结构RepViT| 最新开源移动端网络架构ICCV 2023,1.3ms 延迟,速度贼快

💡本篇内容:剑指YOLOv7改进最新重参数化结构RepViT| 最新开源移动端网络架构ICCV 2023,1.3ms 延迟,速度贼快 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv7 按步骤操作运行改进后的代码即可 💡:重点:该专栏《剑指YOLOv7原创改进》只更新改进 YOLOv7 模型的…

《计算机视觉中的多视图几何》笔记(5)

5 Algorithm Evaluation and Error Analysis 本章主要讲述对算法的验证和误差分析。 概述了两种计算这种不确定性&#xff08;协方差&#xff09;的方法。第一个基于线性近似值&#xff0c;涉及串联各种雅各布表达式&#xff0c;第二个是更容易实施蒙特卡洛方法。 文章目录 …

Pytorch面试题整理(2023.09.10)

1、pytorch如何微调fine tuning&#xff1f; 在加载了预训练模型参数之后&#xff0c;需要finetuning 模型&#xff0c;可以使用不同方式finetune。 局部微调&#xff1a;加载了模型参数后&#xff0c;只想调节最后几层&#xff0c;其他层不训练&#xff0c;也就是不进行梯度…

从命令行管理文件(二)

1.数据流和重定向 1.2数据流 标准输入 (standard input&#xff0c;简称stdin):默认情况下&#xff0c;标准输入指从键盘获取的输入 标准输出(standard output&#xff0c;简称stdout): 默认情况下&#xff0c;命令执行所回传正确的信息会输出到屏幕上 标准错误输出(standard …