在Android中使用Flow获取网络连接信息

在Android中使用Flow获取网络连接信息

如果你是一名Android开发者,你可能会对这个主题感到有趣。考虑到几乎每个应用程序都需要数据交换,例如刷新动态或上传/下载内容。而互联网连接对此至关重要。但是,当用户的设备离线时,数据如何进行交换呢?我们如何确定设备重新连接到互联网,以便我们可以提供他们请求的数据?本文将指导您了解如何读取和监听用户的网络状态。

让我们来深入研究一下!

首先,我们要使用ConnectivityManager类来确定用户设备的网络连接状态。

class MyConnectivityManager(context: Context) {private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)fun isConnected(): Boolean {// Network class represents one of the networks that the device is connected to.val activeNetwork = connectivityManager.activeNetwork return if (activeNetwork == null) {false // if there is no active network, then simply no internet connection.} else {// NetworkCapabilities object contains information about properties of a networkval netCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) (netCapabilities != null// indicates that the network is set up to access the internet&& netCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)// indicates that the network provides actual access to the public internet when it is probed&& netCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) }}
}

然而,连接状态可能随时发生变化,因此如果我们希望监听这些变化,我们可以利用ConnectivityManager.NetworkCallback

class MyConnectivityManager(context: Context) {private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)private val networkCallback = object : ConnectivityManager.NetworkCallback() {override fun onAvailable(network : Network) {// indicates that the device is connected to a new network that satisfies the capabilities // and transport type requirements specified in the NetworkRequest}override fun onLost(network : Network) {// indicates that the device has lost connection to the network.}override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {// indicates that the capabilities of the network have changed.}})fun subscribe() {connectivityManager.registerDefaultNetworkCallback(networkCallback)/*or:val networkRequest = NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build()connectivityManager.registerNetworkCallback(networkRequest, networkCallback)*/}fun unsubscribe() {connectivityManager.unregisterNetworkCallback(networkCallback)}
}

我想分享一些关于以下内容的知识:

ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback)ConnectivityManager.registerNetworkCallback(NetworkRequest, NetworkCallback)
何时使用哪个?这取决于你的需求。

registerDefaultNetworkCallback(NetworkCallback) 用于接收关于我们应用程序默认网络的变化通知。所有应用程序都有一个默认网络,由系统确定。系统通常倾向于选择非计量网络而不是计量网络,并且更喜欢速度更快的网络而不是速度较慢的网络。

registerNetworkCallback(NetworkRequest, NetworkCallback) 则用于只接收特定网络的通知。这就是为什么有 NetworkRequest 来指定需求。

例如,下面的代码用于创建一个请求,该请求连接到互联网并使用Wi-Fi或蜂窝连接作为传输类型。

val networkRequest = NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build()

或者像这样:

val networkRequest = NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build()

NetworkCapabilities.NET_CAPABILITY_NOT_METERED 能力用于不向用户计费的网络。我们鼓励您参考此文档以确定适当的使用方式,并在可能的情况下限制对计量网络的使用,包括将大型下载推迟到连接到非计量网络的时候。你可以了解更多关于 NetworkCapabilities 常量以调整你的需求。

总结一下,使用 registerDefaultNetworkCallback(NetworkCallback) 对默认网络连接进行一般性的监控。然而,这个方法是在API 24(或26,具体取决于是否与Handler一起使用)中添加的。

如果你的应用程序支持最低SDK版本为21,那么使用 registerNetworkCallback(NetworkRequest, NetworkCallback) 更可取。

还有一个在API级别21引入的requestNetwork(NetworkRequest, NetworkCallback)方法,用于查找与指定NetworkRequest匹配的最佳网络。区别在于,register...() 用于监听网络连接的变化,而 request...() 更类似于请求特定网络。

系统将限制每个应用程序(由应用程序UID标识)的未完成网络请求数量为100个,这些请求与 registerNetworkCallback(NetworkRequest, PendingIntent) 及其变体、requestNetwork(NetworkRequest, PendingIntent) 以及 ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback 共享,以避免由于应用程序泄漏回调而导致的性能问题。如果超出了限制,将抛出异常。重要的是取消注册这些回调以避免此问题,并节省资源。

让我们看看所要实现的效果

下线与上线效果
使用Compose实现代码如下:

@Composable
fun ConnectivityUiView(isOnline: Boolean) {Box(modifier = Modifier.fillMaxWidth(),contentAlignment = Alignment.TopCenter,) {val msgStr = stringResource(id = if (isOnline) {R.string.internet_back_online_msg} else {R.string.you_are_offline_msg})val bgColor = if (isOnline) JunglesGreen else Color.GrayText(text = msgStr,modifier = Modifier.fillMaxWidth().background(bgColor).padding(4.dp),style = TextStyle(color = Color.White),textAlign = TextAlign.Center)}
}

接下来是在之前创建的 MyConnectivityManager 类中创建另一个回调函数。

class MyConnectivityManager(context: Context) {...private val networkCallback = object : ConnectivityManager.NetworkCallback() {override fun onLost(network : Network) {mCallback?.onLost()}override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)&& networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {mCallback?.onConnected()}}})private var mCallback: Callback? = nullfun setCallback(callback: Callback) {mCallback = callback}...interface Callback {fun onConnected()fun onLost()}
}

如果你注意到,当具备 NET_CAPABILITY_INTERNETNET_CAPABILITY_VALIDATED 这两种能力时,会调用 mCallback?.onConnected() 回调函数。

为什么不在 onAvailable() 方法中调用呢?答案是,在使用 registerDefaultNetworkCallback() 时,onAvailable() 方法将立即紧随一个对 onCapabilitiesChanged() 的调用。另外,建议不要在这个回调函数中调用 ConnectivityManager.getNetworkCapabilities(android.net.Network) 方法,因为它容易出现竞态条件。

现在我们可以注册 MyConnectivityManager.Callback 来接收通知,无论是 onConnected() 还是 onLost()

class MainActivity : ComponentActivity() {private val myConnectivityManager by lazy { MyConnectivityManager(this) }override fun onCreate(savedInstanceState: Bundle?) {...setContent {var isOnline by remember { mutableStateOf(myConnectivityManager.isConnected()) }val myConnectivityManagerCallback = object : MyConnectivityManager.Callback {override fun onConnected() {isOnline = true}override fun onLost() {isOnline = false}}myConnectivityManager.setCallback(myConnectivityManagerCallback)ConnectivityUiView(isOnline)}}override fun onResume() {...myConnectivityManager.subscribe()}override fun onStop() {...myConnectivityManager.unsubscribe()/*Important! This was only for demo purposes. It's better to unsubscribe inside the onPause() method instead of onStop() to receive the callback only when the UI is visible.*/}
}


然而,有一种情况下,当应用程序恢复(onResume())时,界面没有根据最后的网络状态进行更新,这是因为我们刚刚订阅了 NetworkCallback,并且还没有最新的回调来更新界面。

为了解决这个问题,我们可以通过添加一些“调整”来处理:

fun subscribe() {connectivityManager.registerDefaultNetworkCallback(networkCallback)// everytime an activity or fragment subscribe, we send them the latest state of connectivityif (isConnected()) {mCallback?.onConnected()} else {mCallback?.onLost()}
}

如今的 Android 开发有很多工具使代码更可读、易维护等等。正因为如此,我们将使用 Flow(开心)。

首先,我们将创建一个 Flow,在网络条件下发出 true 或 false。这将取代 mCallback?.onConnected() mCallback?.onLost() 的调用,因为:

每天一个 Flow,远离“回调地狱”。

创建 Flow 有几种方式,但我们将使用 callbackFlow {} 并将其命名为 _connectionFlow

private val _connectionFlow = callbackFlow {val networkCallback = object : ConnectivityManager.NetworkCallback() {override fun onLost(network: Network) {trySend(false)}override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)&& networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {trySend(true)}}}subscribe(networkCallback)awaitClose {unsubscribe(networkCallback)}
}

正如你所看到的,我们使用 trySend(),因为只有在收集器准备好时才需要发送值。另一个选项是在想要发送值并愿意挂起直到可以传递时使用 send()

还要注意的是,我们现在在 Flow 块内部订阅了 networkCallback,并在使用 awaitClose {} 时取消订阅 networkCallback。这是重要的,以确保在不需要订阅时不浪费资源。

上面定义的是一个冷 Flow 实例,这意味着每当将终端操作符应用于结果流时,块都会被调用一次。

现在我们已经在 flow {...} 块中调用了 subscribe()unsubscribe(),所以在 onResume()onPause() 中不再需要调用它们。太好了!保持 DRY,以备将来使用 ✨

接下来,我们将使用 stateIn 将这个冷 Flow 转换成热 Flow(即 StateFlow)。

class MyConnectivityManager(context: Context, private val externalScope: CoroutineScope) {val connectionStateFlow: StateFlow<Boolean>get() = _connectionStateFlow.stateIn(scope = externalScope,started = SharingStarted.WhileSubscribed(5000),initialValue = isConnected)...}

实际上,还有另一种选择可以使用 shareIn()。然而,我认为也许在将来我们可能需要访问最后的网络状态,在这种情况下,使用 StateFlow 更加方便,因为我们可以通过 connectionStateFlow.value 轻松访问它。

将其转换为热 Flow 的原因是我们可以将值从冷上游流多播到多个收集器中。冷流只能有一个订阅者,任何新的订阅者都会创建一个新的flow {..}执行。通过使用热流,我们可以提高性能,因为它们始终处于活动状态,无论是否有观察者,都可以发出数据。

还有一个重要的注意点是热流共享开始的时机。在这种情况下,我们使用了 SharingStarted.WhileSubscribed(5000)。这意味着共享在订阅期间开始,并在最后一个收集器取消订阅后活动状态保持 5 秒钟。

最后,我们如何在我们的 Compose UI 中实现它呢?

class MainActivity : ComponentActivity() {private val myConnectivityManager by lazy { MyConnectivityManager(this, lifecycleScope) }override fun onCreate(savedInstanceState: Bundle?) {...setContent {val connectionState by myConnectivityManager.connectionStateFlow.collectAsStateWithLifecycle()ConnectivityUiView(connectionState)}}}

最终实现的MyConnectivityManager版本如下:

//MyConnectivityManager.kt 
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn/*** Created by meyta.taliti on 23/09/23.*/
class MyConnectivityManager(context: Context, private val externalScope: CoroutineScope) {private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)val connectionAsStateFlow: StateFlow<Boolean>get() = _connectionFlow.stateIn(scope = externalScope,started = SharingStarted.WhileSubscribed(5000),initialValue = isConnected)private val _connectionFlow = callbackFlow {val networkCallback = object : ConnectivityManager.NetworkCallback() {override fun onLost(network : Network) {trySend(false)}override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)&& networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {trySend(true)}}}subscribe(networkCallback)awaitClose {unsubscribe(networkCallback)}}private val isConnected: Booleanget() {val activeNetwork = connectivityManager.activeNetworkreturn if (activeNetwork == null) {false} else {val netCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)(netCapabilities != null&& netCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)&& netCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))}}private fun subscribe(networkCallback: ConnectivityManager.NetworkCallback) {connectivityManager.registerDefaultNetworkCallback(networkCallback)}private fun unsubscribe(networkCallback: ConnectivityManager.NetworkCallback) {connectivityManager.unregisterNetworkCallback(networkCallback)}
}

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

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

相关文章

Flask+Mysql项目docker-compose部署(Pythondocker-compose详细步骤)

一、前言 环境&#xff1a; Linux、docker、docker-compose、python(Flask)、Mysql 简介&#xff1a; 简单使用Flask框架写的查询Mysql数据接口&#xff0c;使用docker部署&#xff0c;shell脚本启动 优势&#xff1a; 采用docker方式部署更加便于维护&#xff0c;更加简单快…

k8s实战之ELK日志管理

首先查看总体流程 首先创建namespace apiVersion: v1 kind: Namespace metadata:name: kube-logging 一、首先创建es.yaml --- apiVersion: v1 #kubernetes API版本,采用最新版本v1 kind: Service #资源类型定义为Service metadata: name: elasticsearch-logging # …

时间Date

你有没有思考过时间问题&#xff1a; 前端为什么可以直接看见时间格式的数据 后端怎么接受的数据&#xff0c;怎么处理的 一般来说&#xff1a;前端传输来数据都是时间格式的字符串&#xff0c;那么后端需要能够解析时间格式的字符串&#xff0c;归功于JSONFormat ,可以解析…

LED靠近语音声光警示灯

应用范围: 适用于高压线塔、施工工地、铁路道口等危险区域。通过微波感应检测人体行动&#xff0c;触发语音警报&#xff0c;做到提前预警&#xff0c;避免可能发生的危险事故。 产品特点 1:设备采用独立太阳能供电系统&#xff0c;警示灯与语音报警装置均为太阳能独立供电&…

【Linux驱动】最基本的驱动框架 | LED驱动

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;最基本的驱动框架⚽驱动程序框架⚽编程 &#x1f3c0;LED驱动⚽配置GPIO⚽编程…

智能优化算法应用:基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.向量加权平均算法4.实验参数设定…

uniapp创建/运行/发布项目

1、产生背景----跨平台应用框架 在移动端各大App盛行的时代&#xff0c;App之间的竞争也更加激烈&#xff0c;他们执着于让一个应用可以做多个事情 所以就应运而生了小程序&#xff0c;微信小程序、支付宝小程序、抖音小程序等等基于App本身的内嵌类程序。 但是各大App他不可…

【ARMv8M Cortex-M33 系列 1 -- SAU 介绍】

文章目录 Cortex-M33 SAU 介绍SAU 的主要功能包括SAU 寄存器配置示例 Cortex-M33 SAU 介绍 在 ARMv8-M 架构中&#xff0c;SAU&#xff08;Security Attribution Unit&#xff09;是安全属性单元&#xff0c;用于配置和管理内存区域的安全属性。SAU 是 ARM TrustZone 技术的一…

JavaScript中的prototype和_proto_的关系是什么

JavaScript中的prototype和_proto_的关系是什么 __proto__ 是 JavaScript 中对象的一个内部属性&#xff0c;它指向该对象的原型。JavaScript 中每个对象都有一个 __proto__ 属性&#xff0c;通过它可以访问对象的原型。prototype 是函数对象特有的属性&#xff0c;每个函数都…

使用PE信息查看工具和Dependency Walker工具排查因为库版本不对导致程序启动报错的问题

目录 1、问题说明 2、问题分析思路 3、问题分析过程 3.1、使用Dependency Walker打开软件主程序&#xff0c;查看库与库的依赖关系&#xff0c;找出出问题的库 3.2、使用PE工具查看dll库的时间戳 3.3、解决办法 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&…

Apache RocketMQ,构建云原生统一消息引擎

本文整理于 2023 年云栖大会林清山带来的主题演讲《Apache RocketMQ 云原生统一消息引擎》 演讲嘉宾&#xff1a; 林清山&#xff08;花名&#xff1a;隆基&#xff09;&#xff0c;Apache RocketMQ 联合创始人&#xff0c;阿里云资深技术专家&#xff0c;阿里云消息产品线负…

正则表达式:元字符

一、什么事元字符 正则是由一系列的元字符组成的&#xff0c;所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符&#xff0c;元字符是构成正则表达式的基本元件。 二、元字符的分类 1.特殊单字符 表达式含义\d匹配任意一个数字\D匹配任意非数字\w匹配任意一个字母、…

.NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布

作者&#xff1a; Jon Galloway - Principal Program Manager, .NET Community Team Mehul Harry - Product Marketing Manager, .NET, Azure Marketing 排版&#xff1a;Alan Wang .NET Conf 2023 是有史以来规模最大的 .NET 会议&#xff0c;来自全球各地的演讲者进行了 100 …

3分钟了解安全数据交换系统有什么用!

企业为了保护核心数据安全&#xff0c;都会采取一些措施&#xff0c;比如做网络隔离划分&#xff0c;分成了不同的安全级别网络&#xff0c;或者安全域&#xff0c;接下来就是需要建设跨网络、跨安全域的安全数据交换系统&#xff0c;将安全保障与数据交换功能有机整合在一起&a…

掌握ElasticSearch(一):Elasticsearch安装与配置、Kibana安装

文章目录 〇、简介1.Elasticsearch简介2.典型业务场景3.数据采集工具4.名词解释 一、安装1.使用docker(1)创建虚拟网络(2)Elasticsearch安装步骤 2.使用压缩包 二、配置1.目录介绍2.配置文件介绍3.elasticsearch.yml节点配置4.jvm.options堆配置 二、可视化工具Kibana1.介绍2.安…

算法leetcode|94. 二叉树的中序遍历(多语言实现)

文章目录 94. 二叉树的中序遍历&#xff1a;样例 1&#xff1a;样例 2&#xff1a;样例 3&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 94. 二叉树的中序遍历&#xff1a; …

51单片机相关寄存器

前言 单片机复习的时候对应寄存器的记忆感觉很混乱&#xff0c;这里进行一下整理,后面的单词是我用来辅助记忆的&#xff0c;可能并不是表示原本的含义。 P3口的第二功能 0RXD 串行数据输入口 1TXD串行数据输出口2INT0外部中断0输入3INT1外部中断1输入4T0定时器0外部计数输入…

2023新能源汽车,吵得越凶,卖得越多

作者 | 辰纹 来源 | 洞见新研社 2023年的汽车行业很残酷&#xff0c;合资大败退&#xff0c;市场份额被自主品牌大幅渗透&#xff0c;三菱退出中国市场&#xff0c;成为真实写照。 新能源车企&#xff0c;威马领头&#xff0c;天际、自游家NIUTRON、恒驰、爱驰、雷丁等造车新…

【大厂面试】之 美团(一面经含答案)

美团 一面 tcp三次握手&#xff0c;四次挥手。time-wait、close-wait状态。MSL代表什么&#xff1f;为什么time-wait是2MSL&#xff0c;可不可以更长&#xff1f;如果不设置time-wait有什么影响 time-wait是主动关闭方的一个状态&#xff1b;close-wait是被动关闭方的一个状态…

贪吃蛇小游戏的代码实现之知识点铺垫篇

今天给大家介绍一个很经典的小游戏&#xff0c;它和扫雷在经典小游戏这方面可以说是旗鼓相当&#xff0c;它的名字就是贪吃蛇。贪吃蛇游戏最初为单机模式&#xff0c;后续又陆续推出团战模式、赏金模式、挑战模式等多种玩法。该游戏具体玩法是&#xff1a;用游戏把子上下左右控…