上一篇我们已经建立了完整的网络异常链路:

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

正式串成一套完整的网络可靠性策略。

Logo

一站式 AI 云服务平台

更多推荐