在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 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.mfbz.cn/a/270725.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

天啦撸 超级麻烦的MySQL索引和数据引擎,快拿小本本记好

1 MySQL的索引 1.1 索引 定义&#xff1a; 索引是一个排序的列表&#xff0c;包含索引字段的值和其对应的行记录的数据所在的物理地址 ●索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过…

使用Maven Archetype插件制作项目脚手架(一)

Archetype是一个Maven项目模板工具包。通过Archetype我们可以快速搭建Maven项目。比如我们在ide里面创建项目时&#xff0c;可以选择很多maven内置的Archetype&#xff0c;我们最常用的可能是maven-archetype-quickstart 当然maven提供了能力&#xff0c;让我们自定义项目结构&…

Electron自定义通知Notification

Notification是什么&#xff1f; 对于渲染进程&#xff0c;Electron 允许开发者使用通知中API&#xff0c;来运行系统的原生通知进行显示。 如何实现系统Notification&#xff1f; const { Notification } require(electron);const isAllowed Notification.isSupported();…

最新ChatGPT商业运营网站程序源码,支持Midjourney绘画,GPT语音对话+DALL-E3文生图+文档对话总结

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

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

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

【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

IIC模块Verilog实现–用IIC协议从FPGA端读取E2PROM 下面是 design 设计 I2C_dri.v module IIC_CONTROL #(parameter SLAVE_ADDR 7b1010000 , // E2PROM 从机地址parameter CLK_FREQ 26d50_000_000 , // 50MHz 的时钟频率parameter …

[内功修炼]函数栈帧的创建与销毁

文章目录 1:什么是函数栈帧2:理解函数栈帧能解决什么问题呢3:函数栈帧的创建与销毁的解析3.1:什么是栈3.2:认识相关寄存器与汇编指令相关寄存器相关汇编指令 3.3 解析函数栈帧的创建和销毁3.3.1 预备知识3.3.2 详细解析一:调用main函数,为main函数开辟函数栈帧First:push前push…

【Python3】\u字符与中文字串互转

小水。 encode和decode&#xff1a; str没有decode函数&#xff0c;但对应的有encode函数&#xff0c;该函数作用是转码为bytes对象bytes通过decode函数转换回对应的str对于一些偏激的(可以用过分来形容)的字符串&#xff0c;例如一二三\\u56db\\u4e94\\u516d&#xff0c;是有…

k8s 组件

k8s: kubernets:8个字母省略&#xff0c;就是k8s. 自动部署&#xff0c;自动扩展和管理容器化的应用程序的一个开源系统。 k8s是负责自动化运维管理多个容器化程序的集群&#xff0c;是一个功能强大的容器编排工具。 以分布式和集群化的方式进行容器管理。 1.20面试版本 …

RPC(6):RMI实现RPC

1RMI简介 RMI(Remote Method Invocation) 远程方法调用。 RMI是从JDK1.2推出的功能&#xff0c;它可以实现在一个Java应用中可以像调用本地方法一样调用另一个服务器中Java应用&#xff08;JVM&#xff09;中的内容。 RMI 是Java语言的远程调用&#xff0c;无法实现跨语言。…

小程序真机如何清除订阅数据

在做小程序订阅消息开发的过程中发现&#xff0c;真机上如果是选择了‘总是保持以上选择’&#xff0c;一旦用户授权后&#xff0c;后面就不会再弹出申请改订阅消息的授权弹窗&#xff0c;这对于开发过程中是很不方便的。 曾试过清除缓存&#xff0c;重进小程序也不能清除掉 解…

爬虫反爬之代码混淆,特殊编码,表情编码

不知道你是否见过这样的代码&#xff0c;完全看不懂。 大家好&#xff0c;这一集我们来看一下前端反爬的代码混淆&#xff0c;一般啊我们自己写的前端代码都是直接上传公开的&#xff0c;如果用的不是框架打包出来的代码&#xff0c;就是自己写的js&#xff0c;html文件没有经过…

T-Dongle-S3开发笔记——创建工程

创建Hello world工程 打开命令面板 方法1&#xff1a;查看->命令面板 方法2&#xff1a;按F1 选择ESP-IDF:展示示例项目 创建helloworld 选择串口 选择芯片 至此可以编译下载运行了 运行后打印的信息显示flash只有2M。但是板子上电flash是W25Q32 4MB的吗 16M-bit

SPFA算法总结

知识概览 SPFA算法是Bellman_Ford算法的优化。时间复杂度一般是O(m)&#xff0c;最坏时间复杂度是O(nm)&#xff08;遇到网格图、菊花图&#xff09;&#xff0c;其中n是点数&#xff0c;m是边数。SPFA算法其实是单源最短路限制最小的算法&#xff0c;只要图中没有负环&#xf…

Mongodb基础介绍与应用场景

NoSql 解决方案第二种 Mongodb MongoDB 是一款开源 高性能 无模式的文档型数据库 当然 它是NoSql数据库中的一种 是最像关系型数据库的 非关系型数据库 首先 最需要注意的是 无模式的文档型数据库 这个需要后面我们看到它的数据才能明白 其次是 最像关系型数据库的非关系型数据…

通过three.js玩转车展项目

1.项目搭建 1.1 创建文件夹 mkdir 文件名1.2 初始化package.json npm init -y1.3 安装打包工具并配置相关依赖 npm i parcel -d在package.json中打包路径和指令 1.4 安装three.js npm i three -d2.项目搭建 2.1 新建index.html&#xff0c;并再index.html引入car.js,在…

2023版本QT学习记录 -6- UDP通信之UDP接收端

———————UDP接收端——————— &#x1f384;动图演示 &#x1f384;发送端通信步骤思维导图 &#x1f384;添加组件 QT core gui network&#x1f384;添加头文件 #include "qudpsocket.h"&#x1f384;创建接收对象 QUdpSocket *recvsocket;&…

在VSCode中使用Git教程

文章目录 提交代码操作分支提交远程库拉取代码参考 介绍一下如何在VSCode中使用Git 首先在VSCode中打开一个项目 打开项目后, 点击下图按钮, 可以引入Git 提交代码 点击 &#xff1b;相当于git add. 下面两张图, 第一张表示改文件后的号, 只会add本文件. 第二张图表示这段时…

Jenkins的邮箱配置和插件下载

启动&#xff1a;java -jar jenkins.war 一定在jenkins.war的目录下 进入cmd命令 浏览器输入网址&#xff1a;http://localhost:8080/login?from%2F 账号&#xff1a;admin 密码&#xff1a;123456 安装插件&#xff1a; 插件更新后重启下 配置邮箱账号&#xff1a; 3…

关于PSINS中涉及到的地球参数更新

地球参数相关的更新函数 void CEarth::Update(const CVect3 &pos, const CVect3 &vn, int isMemsgrade) 用到位置以及速度 那么位置和速度用的哪个时刻的&#xff1f; 假如设计算周期为[T,2T] &#xff1b; 解算时刻为2T时刻&#xff0c;那么地球参数用的是哪一时刻的…
最新文章