补充篇 8.1:Ktor/KMP 断网处理:为什么请求前要先判断网络状态?
上一篇我们已经建立了完整的网络异常链路:
Request
↓
Network / Timeout
↓
HTTP
↓
JSON
↓
Business Code
↓
AppError
但在讨论:
AppError.Network
时,会遇到一个非常实际的问题:
如果当前已经明确断网,为什么还要把 Request 交给 HttpClient,然后等 Engine 请求失败?
以前 Android + Retrofit + OkHttp 项目中,经常会在 Interceptor 中:
Request
↓
Interceptor
↓
检查网络状态
↓
没有网络
↓
直接抛 NoNetworkException
↓
不再 chain.proceed()
也就是说:
已经明确知道当前不具备网络请求条件,就不要继续进入真正的 HTTP 请求链路。
到了 Ktor/KMP,这个思想仍然成立。
但 KMP 还多了一层值得理解的设计:
平台网络状态监听
↓
持续修改 NetworkConnectivityProvider 内部状态
↓
NetworkClient 长期持有同一个 Provider
↓
每次 Request 读取 Provider 当前最新状态
所以这一篇真正要讲清楚三个东西:
Network Monitor
NetworkConnectivityProvider
NetworkClient
分别负责什么。
一、先说结论:断网应该有两道防线
整体可以先记成:
第一道防线
↓
请求前 Pre-check
↓
明确断网
↓
Fail Fast
第二道防线
↓
真正 HTTP 请求
↓
网络过程中仍然可能失败
↓
ExceptionMapper
也就是:
Pre-check 负责快速失败,真实网络异常负责最终兜底。
完整结构:
ApiService
↓
NetworkClient
↓
NetworkConnectivityProvider
↓
请求前 Pre-check
/ \
无网络 有网络
↓ ↓
AppError.Network HttpClient
↓
Engine
↓
真正网络请求
↓
网络仍然可能发生变化
↓
Throwable
↓
ExceptionMapper
↓
AppError
二、为什么请求前检查有价值?
假设手机已经明确没有互联网。
用户点击:
刷新
如果完全不检查:
ViewModel
↓
Repository
↓
ApiService
↓
NetworkClient
↓
HttpClient
↓
Engine
↓
DNS / Connect / Socket
↓
失败
↓
Throwable
↓
ExceptionMapper
↓
AppError.Network
最后当然也能得到:
网络不可用
但是问题是:
在进入 HttpClient 之前,我们其实已经知道这次请求大概率没有执行意义。
所以可以提前:
Request
↓
Connectivity Pre-check
↓
明确没网
↓
直接结束
三、Pre-check 本质上就是 Fail Fast
所谓:
Fail Fast
就是:
已经能够确定当前操作不具备执行条件,就尽早失败,而不是继续经过后面整套流程。
于是:
Request
↓
Pre-check
↓
Unavailable
↓
AppError.Network
而不用继续:
HttpClient
↓
Engine
↓
DNS
↓
Connect
↓
Socket
↓
Timeout
所以请求前网络检查最核心的价值是:
快速失败
+
避免无意义的网络执行链路
四、“节约网络资源”应该怎么理解?
这里可以更准确一些。
设备已经完全断网时,底层本来就未必真的能把 HTTP 数据发送到公网。
所以所谓:
节约网络资源
并不只是:
少用了多少流量
更主要是避免:
不必要的 Engine 工作
DNS 尝试
连接建立尝试
Socket 工作
Timeout 等待
无意义 Retry
额外日志
额外异常转换
最终价值:
Fail Fast
↓
减少无效工作
↓
降低等待时间
↓
改善用户体验
五、这和以前 OkHttp Interceptor 的思想其实一样
以前可能这样:
class NetworkInterceptor(
private val networkChecker: NetworkChecker,
) : Interceptor {
override fun intercept(
chain: Interceptor.Chain,
): Response {
if (!networkChecker.isConnected()) {
throw NoNetworkException()
}
return chain.proceed(
chain.request()
)
}
}
真正应该迁移的不是:
Interceptor 这个 API
而是它背后的职责:
真正执行网络之前
↓
检查必要条件
↓
条件不满足
↓
不继续执行
所以到了 Ktor:
以前:
ConnectivityManager
↓
OkHttp Interceptor
↓
chain.proceed()
现在:
NetworkConnectivityProvider
↓
NetworkClient / Client Plugin
↓
HttpClient
思想是一致的。
六、NetworkConnectivityProvider 到底是什么?
这里是这一版需要重点补充的地方。
不要把:
NetworkConnectivityProvider
理解成:
创建 HttpClient 时传进去一个 Boolean。
例如不是:
创建 Client
↓
networkAvailable = true
↓
以后永远 true
更准确的是:
NetworkConnectivityProvider 是一个长期存在的状态提供对象。
NetworkClient 持有的是:
Provider 对象引用
而不是:
Provider 当时那一刻的值
结构:
创建 NetworkConnectivityProvider
↓
创建 NetworkClient
↓
NetworkClient 持有 Provider
↓
↓
平台网络状态不断变化
↓
持续更新 Provider 内部状态
↓
每次 Request
↓
读取 Provider 当前最新状态
这和我们前面讲过的:
LanguageProvider
TenantProvider
RobotProvider
是同一种 Provider 思想。
七、Provider 对象不变,变的是内部状态
例如:
class DefaultNetworkConnectivityProvider :
NetworkConnectivityProvider {
private var available: Boolean = false
override fun isNetworkAvailable(): Boolean {
return available
}
fun updateNetworkAvailable(
available: Boolean,
) {
this.available = available
}
}
创建一次:
NetworkConnectivityProvider A
NetworkClient:
class NetworkClient(
private val client: HttpClient,
private val connectivityProvider:
NetworkConnectivityProvider,
)
持有的始终是:
Provider A
但 Provider A 内部可以:
available = true
↓
available = false
↓
available = true
所以:
Provider 对象
↓
没有换
Provider 内部状态
↓
持续变化
八、看一个完整状态变化过程
一开始:
Provider
↓
available = true
Request A:
NetworkClient
↓
读取 Provider
↓
true
↓
允许请求
后来网络断开:
Android Connectivity Callback
↓
Provider.update(false)
现在:
还是同一个 Provider
↓
available = false
Request B:
NetworkClient
↓
读取同一个 Provider
↓
false
↓
直接 AppError.Network
网络恢复:
Connectivity Callback
↓
Provider.update(true)
Request C:
NetworkClient
↓
读取 Provider
↓
true
↓
继续 HttpClient
完整过程:
Provider = Available
Request A
↓
允许
网络断开
Provider = Unavailable
Request B
↓
阻止
网络恢复
Provider = Available
Request C
↓
允许
九、所以 Provider 本质上是一个状态源
可以把它理解成:
NetworkConnectivityProvider
↓
保存“当前网络状态”
NetworkClient 每次请求问它:
“现在网络状态是什么?”
就像:
LanguageProvider
↓
“当前默认语言是什么?”
以及:
RobotProvider
↓
“当前操作机器人是谁?”
所以:
Provider 的价值就在于 Client 不需要重新创建,但它每次都可以读取最新状态。
这点非常重要。
十、谁负责修改 Provider?
虽然 Provider 内部状态可以修改,但不应该理解成:
任何地方都可以随便改
例如不建议:
connectivityProvider.available = false
到处出现。
更合理的职责应该是:
平台 Network Monitor
↓
负责感知网络变化
NetworkConnectivityProvider
↓
负责保存当前状态
NetworkClient
↓
只读取
也就是:
Writer
↓
平台网络监听组件
State Holder
↓
NetworkConnectivityProvider
Reader
↓
NetworkClient
这样数据流更清晰。
十一、Provider 对外最好只读
commonMain 可以只暴露:
interface NetworkConnectivityProvider {
val isNetworkAvailable: Boolean
}
实现:
class DefaultNetworkConnectivityProvider :
NetworkConnectivityProvider {
private var _isNetworkAvailable =
false
override val isNetworkAvailable:
Boolean
get() = _isNetworkAvailable
fun update(
available: Boolean,
) {
_isNetworkAvailable = available
}
}
这样:
NetworkClient
↓
只能读
Network Monitor
↓
负责 update
避免整个项目随便修改共享状态。
十二、正式一点甚至可以使用 StateFlow
因为网络状态不仅 NetworkClient 需要。
UI 也可能需要:
Offline Banner
网络恢复提示
重新加载按钮状态
所以可以:
interface NetworkConnectivityProvider {
val networkState:
StateFlow<NetworkState>
val isNetworkAvailable:
Boolean
}
状态:
sealed interface NetworkState {
data object Available :
NetworkState
data object Unavailable :
NetworkState
}
实现概念:
class DefaultNetworkConnectivityProvider :
NetworkConnectivityProvider {
private val _networkState =
MutableStateFlow<NetworkState>(
NetworkState.Unavailable
)
override val networkState:
StateFlow<NetworkState>
get() = _networkState
override val isNetworkAvailable:
Boolean
get() =
_networkState.value ==
NetworkState.Available
fun update(
state: NetworkState,
) {
_networkState.value = state
}
}
于是:
平台 Network Monitor
↓
update
↓
NetworkConnectivityProvider
│
├── NetworkClient
│ ↓
│ 请求前读取
│
└── UI
↓
collect 状态
十三、Provider 不是每次请求都去联网测试
这个非常重要。
不要理解成:
Request
↓
isNetworkAvailable()
↓
Ping 一个网站
↓
再发送业务请求
这反而会:
增加额外网络请求
增加延迟
增加功耗
而且:
测试服务器能访问
≠
业务服务器一定能访问
更合理的是:
平台网络监听
↓
持续维护状态
↓
Provider 保存最新值
Request
↓
只读取 Provider 当前值
也就是:
Monitor 主动更新
Request 被动读取
十四、KMP 为什么特别适合 Provider 抽象?
因为各个平台获取网络状态的方法不同。
概念上:
commonMain
NetworkConnectivityProvider
↑
┌─────────┼─────────┐
↓ ↓ ↓
androidMain iosMain webMain
Android:
ConnectivityManager
NetworkCapabilities
iOS:
NWPathMonitor
Web:
navigator.onLine
online / offline events
但 NetworkClient 不需要知道这些平台 API。
它只知道:
connectivityProvider
.isNetworkAvailable
这就是:
平台能力
↓
Provider 抽象
↓
commonMain 使用
十五、Android 端到底在监听什么?
Android 可以通过:
ConnectivityManager
获得:
Network
NetworkCapabilities
但:
有 Wi-Fi
不等于:
能访问互联网
比如:
手机连接 Wi-Fi
↓
路由器没有公网
所以仅判断:
TRANSPORT_WIFI
是不够的。
十六、INTERNET 和 VALIDATED 要区分
Android 中:
NET_CAPABILITY_INTERNET
更接近:
这个网络被配置成具有访问互联网的能力。
而:
NET_CAPABILITY_VALIDATED
表示系统已经对公网连接进行了实际验证。
所以对于普通:
App
↓
HTTPS
↓
Backend
这种公网 API:
VALIDATED
更接近我们需要的状态。
可以先记:
Wi-Fi Connected
≠
Internet Available
而是:
Network
↓
INTERNET
↓
VALIDATED
逐渐接近真正公网可用。
十七、Android Provider 可以如何更新?
不要每次 Request 都:
connectivityManager.activeNetwork
↓
重新查询一大堆状态
更正式的做法可以是:
ConnectivityManager
↓
NetworkCallback
↓
网络变化事件
↓
更新 Provider
例如:
onAvailable
↓
更新状态
onCapabilitiesChanged
↓
重新判断 VALIDATED
onLost
↓
更新 Unavailable
于是:
Android Network Monitor
↓
持续 update Provider
NetworkClient:
只读取
这正好符合:
Writer / State Holder / Reader
的架构。
十八、Android 结构可以这样理解
ConnectivityManager
↓
NetworkCallback
↓
NetworkCapabilities
↓
判断当前互联网状态
↓
provider.update(...)
↓
NetworkConnectivityProvider
↓
NetworkClient
而不是:
NetworkClient
↓
直接操作 ConnectivityManager
因为后者会把 commonMain 网络层和 Android 平台 API 耦合。
十九、iOS 也是同一个思想
iOS:
NWPathMonitor
↓
网络状态变化
↓
更新 Provider
NetworkClient:
NetworkConnectivityProvider
↓
读取当前状态
所以:
Android
ConnectivityManager
iOS
NWPathMonitor
Web
online/offline
最终做的事情其实都是:
平台变化
↓
修改 Provider 当前状态
二十、Web 也可以监听状态变化
Web:
online
offline
事件变化时:
Browser Event
↓
Provider.update(...)
Request:
NetworkClient
↓
读取 Provider
但 Web 的:
navigator.onLine
只是一个比较弱的网络状态信号。
所以依然要记住:
Provider = Available
并不等于:
API 一定请求成功
二十一、所以 Provider 提供的到底是什么?
更准确的说法不是:
“服务器一定可访问。”
而是:
根据当前平台维护的网络状态,现在是否具备尝试网络请求的条件。
也就是:
Unavailable
↓
明确不值得尝试
Available
↓
可以尝试
↓
但不保证成功
因此:
NetworkConnectivityProvider
本质上提供的是:
Current Network State
而不是:
Future HTTP Result
二十二、NetworkClient 如何使用 Provider?
例如:
class NetworkClient(
private val client: HttpClient,
private val connectivityProvider:
NetworkConnectivityProvider,
)
请求:
private fun ensureNetworkAvailable() {
if (
!connectivityProvider
.isNetworkAvailable
) {
throw NoNetworkException()
}
}
然后:
Request
↓
ensureNetworkAvailable()
↓
读取 Provider 当前状态
注意:
Provider
可能刚刚被平台监听组件更新。
所以 NetworkClient 每次拿到的是:
当前值
而不是:
创建 NetworkClient 时的旧值
二十三、这和我们前面讲 Provider 动态值完全一致
前面:
LanguageProvider
↓
当前默认语言
例如:
zh-CN
↓
切换
↓
en-US
NetworkClient 每次请求重新读取:
当前 Language
现在:
NetworkConnectivityProvider
↓
当前网络状态
例如:
Available
↓
网络断开
↓
Unavailable
↓
恢复
↓
Available
NetworkClient 每次请求也重新读取:
当前 Network State
所以 Provider 可以统一理解为:
一个长期存在、内部状态可以动态变化的数据来源。
二十四、NetworkClient 不需要重新创建
这点非常关键。
假设:
NetworkClient A
创建时:
Provider = Available
后来:
Provider = Unavailable
不需要:
销毁 NetworkClient A
↓
重新创建 NetworkClient B
因为 NetworkClient 持有的是:
Provider 引用
下一次读取自然就是:
Unavailable
所以:
同一个 NetworkClient
同一个 Provider
变化的只是 Provider 内部状态
二十五、完整生命周期可以这样画
App 启动
↓
创建 NetworkConnectivityProvider
↓
创建平台 NetworkMonitor
↓
创建 NetworkClient
↓
NetworkClient 持有 Provider
──────── 运行期间 ────────
网络可用
↓
Monitor
↓
Provider = Available
Request A
↓
读取 Available
↓
请求
网络断开
↓
Monitor
↓
Provider = Unavailable
Request B
↓
读取 Unavailable
↓
Fail Fast
网络恢复
↓
Monitor
↓
Provider = Available
Request C
↓
读取 Available
↓
请求
整个过程中:
NetworkClient
↓
始终没有重建
二十六、为什么 Pre-check 仍然不能代替异常处理?
因为 Provider 保存的本质上只是:
当前网络状态
而网络是动态的。
例如:
10:00:00.000
Provider = Available
Request 读取:
Available
然后:
10:00:00.050
Wi-Fi 突然断开
这时候 Request 已经进入:
HttpClient
↓
Engine
Provider 前面的判断已经完成了。
所以还是可能:
Connection Error
Socket Error
DNS Error
这就是典型的:
检查时正常
≠
执行时一定正常
二十七、所以 Provider 和 ExceptionMapper 职责不同
可以这样记:
NetworkConnectivityProvider
↓
现在是否值得尝试?
HttpClient / Engine
↓
真正执行
ExceptionMapper
↓
实际失败后属于什么错误?
三者不能互相替代。
二十八、完整的两道防线
Request
↓
NetworkConnectivityProvider
↓
当前状态?
/ \
Unavailable Available
↓ ↓
Fail Fast HttpClient
↓ ↓
Network Error Engine
↓
真正执行请求
↓
网络仍可能突然发生变化
↓
Throwable
↓
ExceptionMapper
↓
AppError
所以:
Provider 解决的是当前状态判断,ExceptionMapper 解决的是真实执行失败。
二十九、Pre-check 失败应该怎么进入 AppError?
例如:
if (
!connectivityProvider
.isNetworkAvailable
) {
throw NoNetworkException()
}
然后:
NoNetworkException
↓
ExceptionMapper
↓
AppError.Network
这种方式很好理解。
于是:
Pre-check 发现断网
和
Engine 网络失败
最终都可以:
AppError.Network
业务层不需要区分来源。
三十、Timeout 和断网仍然要分开
如果:
Provider = Unavailable
那么:
直接 Network Error
甚至:
HttpClient 都没执行
自然也不存在:
Connect Timeout
Socket Timeout
Request Timeout
而如果:
Provider = Available
↓
请求进入 Engine
↓
等待超过时间
才会成为:
AppError.Timeout
所以:
明确断网
↓
Network
等待超时
↓
Timeout
不要混。
三十一、Retry 与 Provider 的关系
假设:
Request
↓
失败
↓
准备 Retry
如果此时:
Provider = Unavailable
那继续:
Retry 1
Retry 2
Retry 3
通常没有意义。
所以 Retry 策略可以考虑:
准备下一次 Retry
↓
重新查看 Connectivity State
但这里需要特别注意:
如果 Pre-check 只在 NetworkClient 最外层执行一次,而 Retry 是 Ktor HttpRequestRetry 在 HttpClient 内部完成,那么 Retry 不一定重新经过 NetworkClient 的 Pre-check。
因此:
每次 Retry 前
是否重新检查 Provider
属于后面 HttpRequestRetry 设计时要继续解决的问题。
三十二、Pre-check 放 NetworkClient 还是 Client Plugin?
现在有两个方向。
方案一:NetworkClient
ApiService
↓
NetworkClient
↓
Provider
↓
HttpClient
优点:
简单
清晰
容易和 AppError 体系结合
当前阶段非常合适。
方案二:Ktor Client Plugin
可以把 Connectivity 检查放到:
HttpClient Plugin
某个请求阶段。
概念上:
HttpClient
↓
Connectivity Plugin
↓
Provider
↓
Unavailable
↓
阻止继续发送
它更接近以前:
OkHttp Interceptor
但 Ktor Plugin 的:
Hook / Pipeline
和 OkHttp chain.proceed() 并不是一模一样。
所以现阶段不用急着把 Connectivity 做成 Plugin。
三十三、当前阶段更推荐 NetworkClient
因为我们现在已经有:
ApiService
↓
NetworkClient
↓
HttpClient
那么:
executeRequest()
↓
Pre-check
非常自然。
先把:
职责
生命周期
状态来源
搞清楚。
以后真正学:
Custom Plugin
Pipeline
Hook
再决定是否下沉。
三十四、还有一个很重要的问题:公网和局域网不是一回事
假设:
Android 平板
↓
Wi-Fi
↓
机器人底盘
这个 Wi-Fi:
没有公网
Android 可能:
没有 VALIDATED
但是:
192.168.1.100
机器人完全可以访问。
所以如果简单:
没有 Internet
↓
所有 Request 都拦截
就会出错。
因此:
Internet Connectivity
和:
Local Network Connectivity
必须区分。
三十五、Connectivity 也存在作用域
例如:
apiClient
↓
公网 Backend
uploadClient
↓
公网 Upload Server
robotLocalClient
↓
局域网 Robot
那么:
apiClient
↓
Internet Pre-check
而:
robotLocalClient
↓
不能简单使用 Internet VALIDATED 判断
这和前面我们反复讲的:
公共配置也有作用域
完全一样。
三十六、未来 NetworkConfig 甚至可以描述网络要求
比如:
enum class ConnectivityRequirement {
Internet,
LocalNetwork,
None,
}
然后:
data class NetworkConfig(
val baseUrl: String,
val connectivityRequirement:
ConnectivityRequirement,
)
于是:
api
↓
Internet
robotLocal
↓
LocalNetwork
NetworkClient 根据不同 Client 的职责采取不同 Pre-check。
不过普通 HTTPS 项目暂时不需要设计这么复杂。
三十七、为什么接口不要叫 isWifiConnected()?
因为网络请求可能通过:
Wi-Fi
Mobile
Ethernet
VPN
Local Network
所以真正关注的是:
当前请求所需要的网络能力
而不是:
是不是连接 Wi-Fi
所以:
NetworkConnectivityProvider
比:
WifiChecker
更加合理。
三十八、网络恢复以后 Provider 会怎么变化?
例如:
Unavailable
↓
网络恢复
↓
平台 Monitor 收到变化
↓
Provider.update(Available)
以后新的 Request:
读取 Available
↓
允许执行
这就是 Provider 持续动态更新的价值。
但:
网络恢复
不等于:
之前所有失败请求自动重发
是否 Retry 仍然需要判断:
GET
POST
幂等性
业务状态
特别是:
支付
下单
机器人控制指令
不能简单自动重放。
三十九、所以 Network Monitor 和 NetworkClient 要彻底分开
一个非常清晰的结构是:
平台 NetworkMonitor
↓
感知网络状态变化
↓
update
↓
NetworkConnectivityProvider
↑ ↑
│ │
NetworkClient UI
读取 collect
也就是说:
Monitor
↓
负责写
Provider
↓
负责存
NetworkClient / UI
↓
负责读
这就是很典型的单向状态流。
四十、NetworkConnectivityProvider 和 DefaultRequest Provider 的共同点
现在其实可以把整个 Provider 思想串起来。
LanguageProvider
用户切换语言
↓
Provider 内部状态改变
↓
后续 Request 读取新语言
TenantProvider
当前 Tenant 变化
↓
Provider 状态改变
↓
后续 Request 读取新 Tenant
NetworkConnectivityProvider
网络变化
↓
Provider 状态改变
↓
后续 Request 读取新网络状态
共同点:
Client 长期持有 Provider,而 Provider 长期持有并更新当前状态。
所以:
Provider
不是:
初始化参数快照
而是:
动态状态来源
四十一、但是 Request 特殊值和 ConnectivityProvider 又不同
前面的 Language:
Provider 默认 zh-CN
某一次 Request:
临时 en-US
可以 Request 级覆盖。
但是 Connectivity:
当前明确 Offline
通常不是:
这个 Request 写一个 Header
就能覆盖
因为这是:
执行条件
而不是:
请求参数
所以 Provider 虽然思想相似,但具体业务语义不同。
四十二、完整请求生命周期
最终可以画成:
App 启动
↓
创建 NetworkConnectivityProvider
↓
启动 Platform NetworkMonitor
↓
创建 NetworkClient
↓
NetworkClient 持有 Provider
↓
────── 运行期间 ──────
NetworkMonitor
↓
不断更新 Provider
↓
Request
↓
NetworkClient
↓
读取 Provider 当前状态
↓
┌─────────────┐
│ │
Offline Online
↓ ↓
Fail Fast HttpClient
↓
Engine
↓
真正请求
↓
Throwable / Response
这里最重要的生命周期关系是:
NetworkClient
↓
长期存在
Provider
↓
长期存在
Provider State
↓
持续变化
Request
↓
一次性
四十三、完整 KMP Connectivity 架构
commonMain
NetworkConnectivityProvider
↑
│
当前网络状态 State
↑
┌────────────────┼────────────────┐
│ │ │
androidMain iosMain webMain
│ │ │
ConnectivityManager NWPathMonitor Browser Events
NetworkCallback online/offline
│ │ │
└────────────────┼────────────────┘
↓
更新 Provider State
↓
┌────────────┴────────────┐
↓ ↓
NetworkClient UI
↓ ↓
Pre-check 网络状态展示
↓
HttpClient
↓
Engine
↓
HTTP
这时候职责就非常清楚了。
四十四、本篇总结
断网处理不能只理解成:
Request
↓
HttpClient
↓
请求失败
↓
catch Exception
更完整的设计是:
平台网络状态监听
↓
持续更新 NetworkConnectivityProvider
↓
NetworkClient 每次 Request
读取 Provider 当前最新状态
如果:
Provider = Unavailable
则:
Fail Fast
↓
不进入 HttpClient / Engine
如果:
Provider = Available
只能说明:
当前值得尝试请求
真正能否成功:
仍然由 HttpClient / Engine 实际执行决定
所以:
NetworkConnectivityProvider
↓
解决当前状态
HttpClient / Engine
↓
解决实际执行
ExceptionMapper
↓
解决失败分类
三者职责不同。
另外,Provider 的生命周期也必须理解正确:
NetworkClient
↓
长期持有同一个 Provider
Provider
↓
对象通常长期存在
Provider 内部 NetworkState
↓
可以不断变化
也就是说:
NetworkClient 持有的不是“创建时的网络状态值”,而是一个可持续更新的 NetworkConnectivityProvider 对象;平台网络监听负责修改 Provider 内部状态,NetworkClient 每次请求读取当前最新状态。
这也是 Provider 在整个 KMP 网络架构中的核心价值。
进一步还要记住:
公网 Connectivity
≠
局域网 Connectivity
所以对于:
普通 HTTPS Backend
可以使用:
Internet Pre-check
但对于:
机器人局域网
IoT 设备
局域网服务
必须根据 Client 实际网络职责设计 Connectivity 策略,不能简单用:
Internet Unavailable
一刀切阻止所有网络请求。
回到主线:下一篇
《Ktor Plugin 实战:Logging、HttpTimeout 与 HttpRequestRetry》
下一篇继续把:
Logging
HttpTimeout
HttpRequestRetry
三个正式项目常用能力连接起来。
重点会讲:
HttpTimeout
↓
Request / Connect / Socket
到底分别控制哪一段?
HttpRequestRetry
↓
一次失败为什么可以重新发送?
Provider
↓
第一次请求时 Available
Retry 前
↓
如果 Provider 已经变成 Unavailable
怎么办?
GET
↓
为什么更容易安全 Retry?
POST
↓
为什么 Retry 必须考虑幂等性?
并把目前已经建立的:
Pre-check
↓
真实网络异常
↓
Timeout
↓
Retry
正式串成一套完整的网络可靠性策略。
更多推荐




所有评论(0)