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

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

相关文章

git中的smart checkout和force checkout

切换分支时出现了这个问题&#xff1a; 这是因为shiyan01分支修改了代码,但是没有commit, 所以在切换到test分支的时候弹出这个窗口 一、smart checkout(智能签出) 会把shiyan01分支的改动内容带到test分支。合并处理后的内容就变成了test分支的内容,而shiyan01分支的改动会被…

LH7904D 太阳能警示灯 0.4W×2

应用范围: 可安装在电线杆&#xff0c;路灯&#xff0c;围挡&#xff0c;交 通护栏及各种杆式固体等场所起警示作用。 产品特点&#xff1a; 采用进口PS材质; 光控无开关&#xff0c;白天不闪&#xff0c;昏暗环境自动闪烁&#xff0c;无需手动操作&#xff0c;省时省事; …

Oracle 学习(2)

过滤和排序数据 where条件过滤 日期格式 查询10号部门的员工信息&#xff1a;SQL> select * from emp where deptno10 查询”KING”的信息&#xff1a;SQL> select * from emp where ename KiNg 未选定行。 注意&#xff1a;字符串大小写敏感。 SQL> selec…

Navicat误删除生产环境SQLServer2012单表数据后恢复单表数据

背景&#xff1a; 1-后端更新功能部署到客户生产环境时误将测试环境数据保留&#xff0c;项目负责人发现后告知后端。 2-后端登录客户生产数据库使用navicat删除一张表的单表数据时多删了几条数据&#xff0c;判断弄乱了客户生产环境下自己产生的单表数据。 思路&#xff…

直通车定义、功能以及扣费原则

1.直通车是天猫付费搜索广告&#xff0c;即时需求&#xff0c;是消费者主动来搜索的&#xff0c;cpc扣费原则&#xff0c;一般用来拉新或者收割客户&#xff1b; 2.一般关键词优先&#xff0c;人群溢价是用来更精准投放的&#xff0c;可以不投溢价人群&#xff1b; 3.溢价人群…

什么是数据分析思维

参考 一文学会如何做电商数据分析&#xff08;附运营分析指标框架&#xff09; 电子商务该如何做数据分析&#xff1f;如何数据分析入门&#xff08;从各项指标表象进入&#xff09; https://www.processon.com/outline/6589838c3129f1550cc69950 数据分析步骤 什么是数据分析…

IP 地址归属地查询

IP 地址归属地查询 1. IP 地址归属地查询2. IP 地址归属地查询References 1. IP 地址归属地查询 https://tool.lu/ip/index.html 2. IP 地址归属地查询 https://www.ip.cn/ip/.html References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

【FPGA】分享一些FPGA高速信号处理相关的书籍

在做FPGA工程师的这些年&#xff0c;买过好多书&#xff0c;也看过好多书&#xff0c;分享一下。 后续会慢慢的补充书评。 【FPGA】分享一些FPGA入门学习的书籍【FPGA】分享一些FPGA协同MATLAB开发的书籍 【FPGA】分享一些FPGA视频图像处理相关的书籍 【FPGA】分享一些FPGA高速…

Unity | 渡鸦避难所-4 | 镜头跟随角色移动

1 Cinemachine 简介 在第一人称视角的游戏中&#xff0c;摄像机需要时刻跟随角色移动。除了手动计算摄像机的位置、旋转外&#xff0c;也可以使用 Unity 提供的 Cinemachine 插件来轻松实现摄像机的控制 Cinemachine 是一套用于操作 Unity 相机的模块&#xff0c;解决了跟踪目…

分布式系统架构设计之分布式数据管理

随着互联网时代的不断发展&#xff0c;分布式系统架构成为支撑大规模用户和高并发访问的基础。在构建分布式系统时&#xff0c;分布式系统有着一系列的要求以及对应的核心技术&#xff0c;涉及到数据管理、通信安全性、性能优化、可扩展性设计以及架构演进与版本管理等很多方面…

Shell 脚本基础

Shell脚本 脚本以#!/bin/bash开头 执行方式 直接使用文件名执行&#xff1a;文件需要执行权限 以bash xxx.sh来执行, 本质上是bash解析器去执行, 文件作为一个输入, 因此可以不需要执行权限 变量 系统变量 自定义变量 定义变量 # 定义一个变量username, 注意不能有多余…

实战 9 权限菜单管理

目录 1、权限菜单后端接口 2、查询权限菜单列表 2.1 设计效果图 2.2 menuList.vue 3、 新增权限菜单 3.1 新增权限菜单窗口代码 3.2 选择所属菜单代码 3.3 封装图标选择器 3.4 新增、编辑和删除权限菜单 1、权限菜单后端接口 package com.cizhu.service;import com.ci…

C# WPF上位机开发(子窗口通知父窗口更新进度)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 这两天在编写代码的时候&#xff0c;正好遇到一个棘手的问题&#xff0c;解决之后感觉挺有意义的&#xff0c;所以先用blog记录一下&#xff0c;后…

基于flask和echarts的新冠疫情实时监控系统源码+数据库,后端基于python的flask框架,前端主要是echarts

介绍 基于flask和echarts的新冠疫情实时监控系统 软件架构 后端基于python的flask框架&#xff0c;前端主要是echarts 安装教程 下载到本地&#xff0c;在python相应环境下运行app.py,flask项目部署请自行完成 使用说明 flaskProject文件夹中 app.py是flask项目主运行文…

Android Studio解决:Connect time out问题

目录 前言 一、错误信息 二、解决方法 三、更多资源 前言 当您尝试建立网络连接时&#xff0c;如果连接的建立时间超过了预设的时间限制&#xff0c;就会出现"Connect time out"的错误提示。这通常是由于网络连接问题、服务器故障或网络延迟等原因导致的。 一、…

ARM Cortex-A学习(1):GIC(通用中断控制器)详解

文章目录 1 Cortex-A核中断1.1 处理器模式1.2 IRQ模式 2 GIC的操作2.1 CPU Interface2.2 Distributor GIC(通用中断控制器, Generic Interrupt Controller)是一种用于处理中断的硬件组件&#xff0c;它的主要功能是协调和管理系统中的中断请求&#xff0c;确保它们被正确地传递…

VD6283TX环境光传感器(1)----获取光强和色温

VD6283TX环境光传感器.1--获取光强和色温 概述视频教学样品申请完整代码下载主要特点硬件准备技术规格系统框图生成STM32CUBEMX串口配置IIC配置X-CUBE-ALS演示结果光强测试表 概述 为了充分利用VD6283TX传感器的特性和功能&#xff0c;本章节重点介绍了如何捕获光强度和相关色…

QT foreach

原型&#xff1a;foreach(variable, container) container&#xff1a;容器&#xff0c;即被遍历的对象 variable&#xff1a;当前元素&#xff0c;即遍历container过程中&#xff0c;当前的那个元素 代码&#xff1a; QStringList container { "1", "2&quo…

JVM 类加载子系统

1. 前言 ​ 虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上&#xff0c;虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑&#xff0c;里面可以安装操作系统&#xff1b;程序虚拟机是为了执行单个计算机程序而设计出来…

Linux文件编程

目录 1、Linux系统提供的文件编程API 1.1打开文件&#xff1a;open 1.2创建文件creat函数 1.3写入文件write函数 1.4读取文件read函数 1.5文件光标位置lseek函数 2、另外一组文件编程API 2.1文件打开函数fopen 2.2读文件函数fread 2.3写文件函数fwrite 2.4文件光标位…