Android:使用Service处理息屏后的WebSocket的服务端推送消息并传递给前端
前言
之前我们在使 RESTful 访问服务端时,一般都是客户端请求服务端应答的方式,这种通讯方式,对于需要持续获取数据的情形都是采用轮询的方式,但是这种方式对两边的性能消耗很大,特别是服务端的压力很大。现在当我们使用WebSocket时,这类问题迎刃而解了。服务端可以根据需要向客户端实时推送消息。
WebSocket的协议很简单,客户端的onMessage事件可以很方便的接收消息。我们可以收到后传递给前端的Activity处理更新UI.
现代手机为了省电,屏幕在很短的时候就关闭了,但有时候我们不希望屏幕一灭,应用也跟着睡着了,手机息屏后,我们仍然希望用户能通过语音给用户传递信息。就像你切换屏幕了,高德能继续给你语音导航一样。这种情况单独使用Activity是无能为力的,Android系统设计为只要屏幕一灭,任何Activity的UI活动都会睡眠。不过,息屏后,后台的WebSocket客户端是能够继续接受消息的,但是没办法传到前端的活动了。这时候我们需要引入Android的Service来处理。
在讲解正文之前我们先回顾一下WebSocket的基本使用。
WebSocket与回调接口
在之前的文章中我们介绍了WebSocket的基本使用。就是利用OkHttp3的WebSocket相关功能搭建自己的调用程序框架。这里为了适应Service的使用,我们对WebSocket程序稍微加以改造一下。
首先我们将静态方法改造为实例方法,并使用单例模式。
其次,我们引入一个回调接口,这个很关键。
WebSocketListenerCallback
public interface WebSocketListenerCallback {void onDataReceived(String action, String data);void onFailure(String text);
}
它包含2个方法,
onDataReceived 处理收到消息后怎么办
onFailure 处理通讯失败
我们的WebSocket程序设计为可以设定多个Callback, 在onMessage 收到消息后,逐个执行每个Callback的onDataReceived方法。整个WebSocket程序的代码如下。使用前需要引入Okhttp3库。
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import com.bob.app.C;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;public class WSClient {private static WSClient instance;private WebSocket socket;private final List<WebSocketListenerCallback> callbacks = new ArrayList<>();private final OkHttpClient client;private WSClient() {OkHttpClient.Builder cb = new OkHttpClient.Builder();cb.readTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);cb.connectTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);cb.writeTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);client = cb.build();}public static synchronized WSClient getInstance() {if (instance == null) {instance = new WSClient();}return instance;}public synchronized void init() {Request request = new Request.Builder().url(C.serverUrl).build();WSListener listener = new WSListener();socket = client.newWebSocket(request, listener);}public void addCallback(WebSocketListenerCallback callback) {if (!callbacks.contains(callback)) {callbacks.add(callback);}}public void removeCallback(WebSocketListenerCallback callback) {callbacks.remove(callback);}public synchronized void send(String action, Object data) {long txNo = System.currentTimeMillis();String message = action + "$$" + txNo + "$$" + U.toJSONString(data);if (socket != null) {socket.send(message);}}public void disconnect(int code, String reason) {if (socket != null) {socket.close(code, reason);socket = null;}}private class WSListener extends WebSocketListener {@Overridepublic void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {for (WebSocketListenerCallback callback : callbacks) {String[] str = text.split("\$\$");String action = str[0];//操作指令,String message = str.length > 2 ? str[2] : "";//信息正文callback.onDataReceived(action, message);}}@Overridepublic void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {for (WebSocketListenerCallback callback : callbacks) {callback.onFailure("Error: " + t.getMessage());//错误信息传出去}}}
}
C是一个常数类,里面有些常数,比如SOCKET_TIMEOUT = 5;
Service接收推送消息
关于Service的基本使用,网上有很多资料。我们这里通过实际的场景应用来消化理解。
我们现在的应用场景就是:收到后台消息后,通过Service传递给前端应用,然后语音播报。
首先自己的Service继承基类,并且需要实现回调方法WebSocketListenerCallback
在初始化WebSocket的时候我们就把Callback传进去。并初始化。
整个Service的完整代码如下:
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;import com.bob.app.common.WSClient;
import com.bob.app.common.WebSocketListenerCallback;public class ActiveDataService extends Service implements WebSocketListenerCallback {@Overridepublic void onCreate() {super.onCreate();WSClient wsClient = WSClient.getInstance();//获取实例wsClient.addCallback(this);//传入CallbackwsClient.init();//执行初始化}public void onDataReceived(String action, String str) {if (A.ACTIVE_DATA.equals(action)) {//如果指令是推送新消息Intent intent = new Intent(BC.BROADCAST_ACTIVE_DATA);//收到最新实时消息intent.putExtra("data", str);LocalBroadcastManager.getInstance(this).sendBroadcast(intent);}}public void onFailure(String str) {Intent intent = new Intent(BC.BROADCAST_SERVER_OFFLINE);//服务端访问异常LocalBroadcastManager.getInstance(this).sendBroadcast(intent);}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {startForeground(1, createNotification());return START_STICKY;}private Notification createNotification() {NotificationChannel channel = new NotificationChannel("channel", "Service", NotificationManager.IMPORTANCE_LOW);NotificationManager manager = getSystemService(NotificationManager.class);if (manager != null) {manager.createNotificationChannel(channel);}NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel").setContentTitle(getString(R.string.app_name)).setContentText(getString(R.string.fetching_data)).setSmallIcon(android.R.drawable.ic_notification_overlay).setAutoCancel(true).setOngoing(false).setPriority(NotificationCompat.PRIORITY_DEFAULT); // Make sure the priority is LOWreturn builder.build();}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onDestroy() {super.onDestroy();}
}
其中BC.BROADCAST_ACTIVE_DATA和BC.BROADCAST_SERVER_OFFLINE是2个常数字符串,可以自己取名字。
在Android里面,由于Activity和Service的生命周期并不相同,Service息屏后还能活动,因此Service并不能直接引用Activity也不能直接与Activity交互,只能通过广播间接交互。意思就是Service发送广播,Activity可以订阅后处理。上面的代码展示了怎样发送广播。
createNotification是一个常规方法,创建一个通知信息栏。这个会让用户知道一个后台服务在干嘛。有的手机会显示比如华为等,有的手机也默认不显示,比如Oppo等
Activity接收Service广播信息
Service发送的广播,Activity可以通过订阅的方式来使用。
具体的订阅方法如下:
private final BroadcastReceiver receiver1 = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String data = intent.getStringExtra("data");finishGetActiveData(data);//接收到最新数据,传递到处理方法}};private final BroadcastReceiver receiver2 = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {showLogin();//接收到网络异常,退回到登录界面}};
先声明BroadcastReceiver, receiver的目的就是声明收到数据后怎么办。在onReceive方法里接收数据,然后传递到实现方法里面。这里我们声明了2个receiver,一个是收到正常的最新信息后怎么办。另一个是收到网络错误信息后怎么办。
然后在onCreate方法里注册receiver
Intent serviceIntent = new Intent(this, ActiveDataService.class);
ContextCompat.startForegroundService(this, serviceIntent);
LocalBroadcastManager.getInstance(this).registerReceiver(receiver1, new IntentFilter(BC.BROADCAST_ACTIVE_DATA));
LocalBroadcastManager.getInstance(this).registerReceiver(receiver2, new IntentFilter(BC.BROADCAST_SERVER_OFFLINE));
注意其中的 IntentFilter的用法,一般一个receiver只过滤接收一种消息。
这样我们就完成把消息传送给Activity这个数据链路了。
然后我们可以在finishGetActiveData方法里面干该干的事情了,比如调用TTS语音播报啥的。
看到这里,有的小伙伴可能会问,这个Callback我能不能在Activity里将WebSocket初始化,然后传入Callback呢,答案是可以的,可以将Activity实现WebSocketListenerCallback后传入WebSocket,也是一样的效果,还更简单,不用广播。不过前面我们讲过,这种直接使用Activity初始化WebSocket的程序只能在亮屏时功能正常,息屏了程序就跟着睡眠了。
另外,需要强调的一点时,要实现息屏后服务正常,需要手机在设置里关闭省电功能,并且允许应用后台活动。否则写了也白搭。只有用户才有最终决定权。
总结
要想息屏后,部分手机功能正常接收后台推送消息并传递给Activity处理,需要做如下处理:
- WebSocket引入回调接口并在onMesssge中调用。
- 创建一个Service实现回调接口,在回调方法中发送广播
- Activity中声明接收广播的receiver,并实现处理receiver收到的的数据。
相关文章:

Android:使用Service处理息屏后的WebSocket的服务端推送消息并传递给前端
前言 之前我们在使 RESTful 访问服务端时,一般都是客户端请求服务端应答的方式,这种通讯方式,对于需要持续获取数据的情形都是采用轮询的方式,但是这种方式对两边的性能消耗很大,特别是服务端的压力很大。现在当我们使…...

Git Bash Here 中文显示乱码的处理方法
在使用"open Git Bash Here"时,遇到中文显示乱码问题。 原因:通常是由于编码设置不正确导致的。 open Git Bash Here —>鼠标右击空白处,点击「选项」|或「Options」 在「文本」或 「Text」选项卡中,找到"local…...

FreeBSD安装教程
FreeBSD 是一个功能强大且可靠的开源 UNIX 操作系统,适合服务器和桌面环境。本文将介绍如何安装 FreeBSD,从系统准备到基础设置,为你快速上手提供帮助。 一、准备工作 1. 硬件要求 CPU:支持 x86 或 AMD64 架构的处理器。 内存&a…...

Loki 各模式简介
目录 Loki 部署模式 单片模式 简单可扩展 微服务模式 Loki 部署模式 Loki 是一个由许多微服务组成的分布式系统。它还具有独特的构建模型,其中所有这些微服务都存在于同一个二进制文件中。 您可以使用命令行标志配置单个二进制文件的行为-target,以指…...

MySQL八股-全局锁,表级锁,表锁,元数据锁,意向锁,行级锁,行锁,间隙锁,临键
文章目录 全局锁表级锁表锁(表级锁)元数据锁(MDL,Meta Data Lock,表级锁)元数据锁演示元数据锁兼容的情况元数据锁互相阻塞的情况 意向锁(Intention lock,表级锁)意向锁分类意向锁演示:意向共享锁(**IS**)与…...

(四)Spring Cloud Alibaba 2023.x:高效构建 Gateway 网关服务
目录 前言 准备 项目集成 pom.xml引入依赖 启动类 yml文件添加网关配置 修改消费者FeignService类 结果验证 前言 Spring cloud alibaba 体系中构建微服务,我们使用Spring Cloud Gateway 作为服务网关, Gateway是Spring 官方推出的一款基于 Web…...

Android XR 是什么?解释它的功能、设备、开发工具等
什么是“Android XR”? Android XR是最新配备AI的OS(操作系统),兼容耳机和眼镜(AR眼镜)。 沉浸式剧场 从视频列表中选择... 您可以体验完全身临其境的视频观看体验。 无限工作空间 您的现实世界将成为您…...

【算法】实体关系抽取
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...

Codeforces Round 993 (Div. 4)题解
A. Easy Problem 思路:经过看了一眼,我们发现,ab的和一定是n,且两个都是正整数, 所以a的范围就是从1~n-1 所以输出n-1即可 #include<bits/stdc.h> using namespace std; #define int long long int t; int n…...

【计算机网络】期末考试预习复习|中
作业讲解 转发器、网桥、路由器和网关(4-6) 作为中间设备,转发器、网桥、路由器和网关有何区别? (1) 物理层使用的中间设备叫做转发器(repeater)。 (2) 数据链路层使用的中间设备叫做网桥或桥接器(bridge)。 (3) 网络层使用的中间设备叫做路…...

从零用java实现 小红书 springboot vue uniapp (4)个人主页优化
前言 移动端演示 http://8.146.211.120:8081/#/ 前面的文章我们基本完成了详情页开发 今天我们具体的去进行实现个人中心 并且分享我开发时遇到的问题 首先先看效果 我们对布局整体规划一下 个人名片 半透明背景 刚开始我用的是 <view style"background-image: ur…...

为“行车大脑”降温:Simdroid-EC助力汽车ECU设计研发
ECU(Electronic Control Unit,电子控制单元)被誉为汽车的行车大脑,在工作时会产生大量的热量,而其散热存在以下难题:一是工作环境恶劣,ECU常处于高温环境中;二是ECU所处的空间较为狭…...

视频汇聚平台:Liveweb视频流媒体平台视频监控系统解决方案
数字化技术在安防领域的广泛应用已经成为公安等重要执法部门的重要趋势,主要得益于无线网络通信技术和计算机技术的快速进步。传统的视频监控系统存在诸多局限,例如只能进行现场监视,报警信息传输简单,无法远距离传输视频信号&…...

通过解调使用正则化相位跟踪技术进行相位解包裹
1. 绪论 光学计量学通常使用光学干涉仪来测量各种物理量。1,2 根据应用的不同,可以使用多种类型的干涉仪,但它们的共同目标是产生一个由被测物理量调制的条纹图案。使用这种光束编码程序可以检测到的物理量范围非常广:深度测量、应变分析、温…...

VMware替代 | 双一流大学采用ZStack ZSphere虚拟化平台加速医学应用算法分析
某双一流大学医学部在面对日益增长的医学应用算法分析需求时,选择采用ZStack ZSphere虚拟化平台,以满足其高性能计算和GPU业务应用的迫切需求。该平台凭借其轻量化、卓越性能及易用性,成功解决了医学部在虚拟化及GPU应用场景中的挑战。随着平…...

UNIAPP框架uView初步集成与开发设计
uView UI,是uni-app生态最优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。本文章分享UNIAPP集成使用uView页面动态开发设计。 一、使用HBuilder X 直接导入插件,下载后重启 uView - DCloud 插件市场 二、配置样…...

C05S08-LVS负载均衡
一、LVS 1. LVS概述 LVS(Linux Virtual Server、Linux虚拟服务)是一种基于Linux系统集群的负载均衡方案,属于四层的负载均衡。 集群:将相同组件部署在不同的服务器上,提供统一的服务,以及同样的功能&…...

C 语言代码诗韵:数字功能的雅集华章
函数基本操作练习 主要内容: 本任务主要练习函数的申请、定义、调用等,主要包含以下功能: 1)编写函数,输入一个整数,求各个数字之和; 2)编写函数,计算1!2&…...

ps案例制作
宣传海报 暖色调海报商品展示图...

【C++】列表初始化、声明、范围for、array容器
列表初始化、声明、范围for、array容器 一、统一的列表初始化1.1 使用{ }初始化1.2 initializer_list容器 二、声明2.1 auto关键字2.2 decltype关键字2.3 nullptr关键字 三、范围for四、array容器和forward_list容器 一、统一的列表初始化 1.1 使用{ }初始化 在C98中…...

C++智能指针详解
一、智能指针简介 智能指针是一个类似于指针的类,将指针交给这个类对象进行管理,我们就可以像使用指针一样使用这个类,并且它会自动释放资源。 智能指针运用了 RAII 的思想(资源获得即初始化)。RAII 是指,用对象的生命周期来管理资…...

基础库正则表达式
我们已经可以用requests 库来获取网页的源代码,得到 HTML 代码。但我们真正想要的数据是包含在 HTML代码之中的,要怎样才能从 HTML,代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。 本篇博客我们将了解一下正则表达式的相关用法。正则表达…...

【spring专题】spring如何解析配置类和扫描包路径
文章目录 目标重要的组件加载配置类启动解析组件定位配置类解析配置类 扫描过程总结 目标 这是我们使用注解方式启动spring容器的核心代码 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(MyConfig.class); User user (Us…...

MyBatis框架的入门
目录 MyBatis第一章:框架的概述1. MyBatis框架的概述 第二章:MyBatis的入门程序1. 创建数据库和表结构2. MyBatis的入门步骤 MyBatis 第一章:框架的概述 1. MyBatis框架的概述 MyBatis是一个优秀的基于Java的持久层框架,内部对…...

代码随想录D22-23 回溯算法01-02 Python
理论回顾 回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。 回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝…...

【网络云计算】2024第50周-每日【2024/12/13】小测-理论-写10个Bash Shell脚本-解析
文章目录 1. 计算1到100的和2. 列出当前目录下所有文件和文件夹3. 检查文件是否存在4. 备份文件到指定目录(简单示例)5. 打印系统当前日期和时间6. 统计文件中的行数7. 批量重命名文件(将.txt后缀改为.bak)8. 查找进程并杀死&…...

MATLAB转换C语言--问题(一)FFT 和 IFFT 的缩放因子
1. MATLAB 中的 FFT 和 IFFT 在 MATLAB 中,fft 和 ifft 函数具有以下缩放行为: fft:执行快速傅里叶变换(FFT),不进行缩放。ifft:执行逆快速傅里叶变换(IFFT),…...

轻松上手:使用 Vercel 部署 HTML 页面教程
😀 在学习前端的过程中,部署项目往往是一个令人头疼的问题。然而,Vercel 为我们提供了一个便捷且免费的解决方案。 Vercel 是一个强大的云平台,专门用于前端项目的部署和托管。它不仅支持多种前端框架和静态网站生成器࿰…...

如何运用 HTM?
一、HTM 概述 HTM(Hierarchical Temporal Memory,分层时序记忆)是一种基于神经科学原理构建的计算模型,旨在模拟大脑的学习和记忆机制,以处理复杂的时间序列数据和模式识别任务。它具有独特的架构和算法,能…...

12.16【net】【study】
路由表是路由器或者其他互联网网络设备上存储的一张表,它记录了到达特定网络目的地的路径。路由表中的每一行(即一个路由条目)包含了目的地网络地址、子网掩码、下一跳地址、出接口等信息。 Destinations(目的地)和 R…...