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

news2025/2/8 21:23:56

在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 network
            val 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.Gray
        Text(
            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? = null

    fun 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 connectivity
    if (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: Boolean
        get() {
            val activeNetwork = connectivityManager.activeNetwork
            return 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.coloradmin.cn/o/1336008.html

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

相关文章

Flink电商实时数仓(六)

交易域支付成功事务事实表 从topic_db业务数据中筛选支付成功的数据从dwd_trade_order_detail主题中读取订单事实数据、LookUp字典表关联三张表形成支付成功宽表写入 Kafka 支付成功主题 执行步骤 设置ttl&#xff0c;通过Interval join实现左右流的状态管理获取下单明细数据…

OGG-MySQL无法正常同步数据问题分析

问题背景: 用户通过OGG从源端一个MySQL从库将数据同步到目标端的另一个MySQL数据库里面&#xff0c;后面由于源端的从库出现了长时间的同步延时&#xff0c;由于延时差距过大最后选择通过重建从库方式进行了修复 从库重建之后&#xff0c;源端的OGG出现了报错ERROR OGG-0014…

电商数据分析-02-电商业务介绍及表结构

参考 电商业务简介 大数据项目之电商数仓、电商业务简介、电商业务流程、电商常识、业务数据介绍、电商业务表、后台管理系统 举个例子:&#x1f330; 1.1 电商业务流程 电商的业务流程可以以一个普通用户的浏览足迹为例进行说明&#xff0c;用户点开电商首页开始浏览&…

蓝桥杯备赛 day 1 —— 递归 、递归、枚举算法(C/C++,零基础,配图)

目录 &#x1f308;前言 &#x1f4c1; 枚举的概念 &#x1f4c1;递归的概念 例题&#xff1a; 1. 递归实现指数型枚举 2. 递归实现排列型枚举 3. 递归实现组合型枚举 &#x1f4c1; 递推的概念 例题&#xff1a; 斐波那契数列 &#x1f4c1;习题 1. 带分数 2. 反硬币 3. 费解的…

小程序面试题 | 18.精选小程序面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

大师计划1.0 - log2 CRTO笔记

CRTOⅠ笔记 log2 这个笔记是我在2023年11月23日-12月22日中&#xff0c;学习CRTO所做的一些笔记。 事实上TryHackMe的路径和htb学院包含了许多CRTO的知识并且甚至还超出了CRTO&#xff08;CS除外&#xff09;&#xff0c;所以很多东西在THM和htb学院学过&#xff0c;这次CRTO等…

RK3588平台开发系列讲解(AI 篇)RKNN rknn_query函数详细说明

文章目录 一、查询 SDK 版本二、查询输入输出 tensor 个数三、查询输入 tensor 属性(用于通用 API 接口)四、查询输出 tensor 属性(用于通用 API 接口)五、查询模型推理的逐层耗时六、查询模型推理的总耗时七、查询模型的内存占用情况八、查询模型里用户自定义字符串九、查询原…

往年面试精选题目(前50道)

常用的集合和区别&#xff0c;list和set区别 Map&#xff1a;key-value键值对&#xff0c;常见的有&#xff1a;HashMap、Hashtable、ConcurrentHashMap以及TreeMap等。Map不能包含重复的key&#xff0c;但是可以包含相同的value。 Set&#xff1a;不包含重复元素的集合&#…

第四周:机器学习知识点回顾

前言&#xff1a; 讲真&#xff0c;复习这块我是比较头大的&#xff0c;之前的线代、高数、概率论、西瓜书、樱花书、NG的系列课程、李宏毅李沐等等等等…那可是花了三年学习佳实践下来的&#xff0c;现在一想脑子里就剩下几个名词就觉得废柴一个了&#xff0c;朋友们有没有同感…

Linux操作系统基础知识点

Linux是一种计算机操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年首次发布。Linux操作系统通常与GNU套件一起使用&#xff0c;因此也被称为GNU/Linux。它是一种类UNIX的操作系统&#xff0c;设计为多用户、多任务…

滤波器(Filter)

滤波器 常用滤波器元器件 馈通电容滤波器NFM18PC104R1C3 \SDCW2012-2-900TF \ 0603 0.1UF(104) 16V 文章目录 滤波器前言一、滤波器是什么二、两路 0805共模滤波器 阻抗90Ω@100MHz三、0603 0.1UF(104) 16V四、馈通电容滤波器NFM18PC104R1C3总结前言 滤波器在电子系统中具有…

车载网络 - BootLoader - UDS刷写闲聊

聊升级的话,我们不得不聊一下MCU升级的一些基础概念;我们今天就简单说下,如果大家有兴趣,可以评论区留言,我后续继续补充内容或者私聊都可以的。 目录 一、MCU内存说明 二、常见的2类BOOT段 三、常见的APP段

机器人制作开源方案 | 森林管理员

​作者&#xff1a;李佳骏、常睿康、张智斌、李世斌、高华耸 单位&#xff1a;山西能源学院 指导老师&#xff1a;赵浩成、郜敏 1. 研究背景 森林作为地球上可再生自然资源及陆地生态的主体&#xff0c;在人类生存和发展的历史中起着不可代替的作用&#xff0c;它不仅能提供…

比宜德停业,奥乐齐死磕,硬折扣该怎样长硬不衰?

作者 | 楚文龙 来源 | 洞见新研社 刚刚过去的周末&#xff0c;让零售行业的从业者神经紧绷。因为&#xff0c;12月23日多个信源曝出&#xff0c;社区硬折扣超市比宜德已公告于12月22日起暂停营业。 作为中国第一家&#xff0c;也是唯一一家规模最大的硬折扣社区连锁店零售商&…

FLStudio21中文版水果编曲软件好用吗?如何下载最新版本

FL Studio21版是一款在国内非常受欢迎的多功能音频处理软件&#xff0c;我们可以通过这款软件来对多种不同格式的音频文件来进行编辑处理。而且FL Studio 21版还为用户们准备了超多的音乐乐器伴奏&#xff0c;我们可以直接一键调取自己需要的音调。 FL Studio21版不仅拥有非常…

leetcode——打家劫舍问题汇总

本章汇总一下leetcode中的打家劫舍问题&#xff0c;使用经典动态规划算法求解。 1、梦开始的地方——打家劫舍&#xff08;★&#xff09; 本题关键点就是不能在相邻房屋偷东西。 采用常规动态规划做法&#xff1a; 根据题意设定dp数组&#xff0c;dp[i]的含义为&#xff1a…

【WPF.NET开发】创建样式

本文内容 创建样式隐式应用样式显式应用样式以编程方式应用样式扩展样式TargetType 属性与 x:Key 属性之间的关系 使用 Windows Presentation Foundation (WPF)&#xff0c;可以使用自己的可重用样式自定义现有控件的外观。 可以对应用、窗口和页面全局应用样式&#xff0c;也…

【自定义磨砂动态背景】前端及pyqt6实现

如何实现一个自定义的磨砂动态背景呢&#xff1f; 这种效果看起来特别的高端&#xff0c;很新颖美观。 具体的效果可以看这里的演示&#xff1a;https://www.bilibili.com/video/BV1zj411H7wd/ 其实原理就是底层有多个多彩多边形在移动&#xff0c;然后再盖上一层模糊滤镜。 前…

【DevOps 工具链】搭建 项目管理软件 禅道

文章目录 1、简介2、环境要求3、搭建部署环境3.1. 安装Apache服务3.2. 安装PHP环境&#xff08;以php7.0为例 &#xff09;3.3. 安装MySQL服务 4、搭建禅道4.1、下载解压4.2、 配置4.2.1、 启动4.2.2、自启动4.2.3、确认是否开机启动 5、成功安装 1、简介 禅道是国产开源项目管…

React Router有几种模式?实现原理?

面试官&#xff1a;说说React Router有几种模式&#xff1f;实现原理&#xff1f; 一、是什么 在单页应用中&#xff0c;一个web项目只有一个html页面&#xff0c;一旦页面加载完成之后&#xff0c;就不用因为用户的操作而进行页面的重新加载或者跳转&#xff0c;其特性如下&a…