ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

KMP网络层分层设计:Android/iOS/桌面端统一网络请求架构

KMP网络层分层设计:Android/iOS/桌面端统一网络请求架构 1. 项目概述为什么KMP成了Android网络层的新基建“AndroidKMP之网络请求”——这六个字背后不是又一个“Kotlin Multiplatform Retrofit”的简单拼接而是一次对Android工程架构底层逻辑的重新校准。我从2019年第一批在生产环境落地KMP的团队开始跟进到2023年主导三个跨端App的KMP网络模块重构踩过的坑比写过的代码还多。今天说的不是“怎么用KMP发个HTTP请求”而是当业务模块要同时跑在Android、iOS、桌面端甚至未来可能的车载系统上时网络请求这一最基础的能力该如何设计才能既不重复造轮子又不被平台差异拖垮交付节奏核心关键词“Android”“KMP”“网络请求”必须放在一起理解——它不是Android专属技术也不是纯Kotlin语法糖更不是把Java代码改成Kotlin就完事。真正的难点在于如何让一套网络逻辑在Android上能无缝接入OkHttp拦截器链、支持WorkManager后台重试、适配Android 12的网络权限变更在iOS上又能调用原生NSURLSession配置TLS证书钉扎、处理后台唤醒限制同时还要让桌面端Compose Desktop不因缺少Context而崩溃让Web端WASM不因CORS被拦死。这不是功能复用是能力抽象。我见过太多团队把KMP网络层做成“Retrofit接口定义Ktor客户端实现”的缝合怪结果Android侧加个Cookie持久化要改三处iOS侧加个自签名证书信任要重编译整个模块最后发现所谓“共享”只剩50%的DTO类。真正稳定的KMP网络方案必须从协议层就开始分层数据协议JSON/Protobuf、传输协议HTTP/HTTPS/gRPC、平台协议Android Intent广播通知、iOS Notification Center回调、Desktop Tray弹窗。这三层里只有数据协议能100%共享传输协议需按平台定制平台协议则完全隔离。适合谁读如果你正面临这些场景团队刚引入KMP但网络模块还在各端各自为政每次接口变更都要同步改四套代码App已上线但用户投诉“iOS上传失败率比Android高3倍”排查发现是iOS后台任务超时策略没对齐架构师在评审新需求时被问“这个网络错误提示要不要加埋点”结果发现Android有Crashlytics、iOS用Firebase、桌面端连日志都没统一入口或者你只是个Android开发者想搞懂为什么同事写的KMP网络层在真机调试时总报error: 上传失败:网络请求错误而模拟器却一切正常。这篇文章不讲KMP环境搭建网上教程够多不堆砌API文档官方文档更全只聚焦一件事把“AndroidKMP之网络请求”从一个标题变成你能立刻抄作业、能预判风险、能说服团队的技术方案。后面所有内容都来自我们给某电商App做KMP网络层重构时的真实日志、抓包记录和灰度数据——包括那个让测试同学连续三天睡不着觉的async upload fail error: 代码包大小超过限制问题根源根本不在网络层而在KMP模块的Gradle依赖树里混进了两个版本的okio。2. 整体架构设计分层不是选择题是生存必需2.1 为什么必须放弃“一套代码打天下”的幻想先说结论KMP网络层的终极目标不是代码行数减少而是故障域隔离。我见过最典型的反面案例是某金融App的KMP网络模块——他们把所有逻辑包括OkHttp拦截器、CookieJar、SSL配置全写在commonMain里用expect/actual强行桥接平台差异。结果上线后出现诡异问题Android端某些机型上传大文件必失败iOS端偶发TLS握手超时而桌面端根本无法启动。根因分析后发现Android侧用了OkHttpClient.Builder().cookieJar(CookieJar)但KMP commonMain里定义的CookieJar接口actual实现里漏写了androidx.startup.Initializer初始化逻辑导致首次请求时CookieJar为空iOS侧的actual实现调用了NSURLSessionConfiguration.default但没设置timeoutIntervalForResource而金融业务要求上传超时必须≤30秒系统默认值是60秒桌面端用Compose Desktop但commonMain里引用了kotlinx-coroutines-android导致JVM运行时找不到Dispatchers.Main。这些问题单看都不难解但它们暴露了一个致命设计缺陷把平台强相关逻辑塞进共享层等于把所有平台的故障概率乘在一起。就像把三台不同型号的发动机硬装进同一辆汽车底盘——表面能跑但任何一台出问题都会让整车抛锚。所以我们的架构第一原则严格分层物理隔离。不是用文件夹命名区分而是用Gradle模块强制约束:network:api纯数据协议层只含DTO、Request/Response契约、序列化器Json/Protobuf100% Kotlin/JVM/JS/WASM兼容:network:transport传输协议层按平台拆分为androidMain、iosMain、jvmMain各自封装OkHttp、NSURLSession、Apache HttpClient:network:platform平台协议层完全不共享Android用BroadcastReceiver监听网络状态iOS用NWPathMonitor桌面端用SystemTray。提示别被“KMP共享”这个词带偏。KMP的价值不在于“写一次到处跑”而在于“改一处所有平台自动受益”。比如修改订单DTO字段确实只需改api模块但若要加个全局请求头如X-App-Version就必须在每个transport模块里分别实现——这恰恰是好事因为Android需要从BuildConfig.VERSION_NAME取值iOS要从Bundle.main.object(for: CFBundleShortVersionString)读桌面端则可能从System.getProperty(app.version)获取强行统一反而增加耦合。2.2 分层后的模块依赖关系与Gradle配置实际项目中模块依赖必须用Gradle的api/implementation精准控制否则会出现“本该隔离的代码意外泄露”。以下是我们在电商App中验证过的最小可行配置// :network:api/build.gradle.kts kotlin { jvm() iosX64() iosArm64() js(IR) { browser() } // 注意这里不声明android()因为Android-specific代码在transport模块 sourceSets { val commonMain by getting { dependencies { implementation(io.ktor:ktor-client-content-negotiation:2.3.10) implementation(io.ktor:ktor-serialization-kotlinx-json:2.3.10) } } } }// :network:transport/build.gradle.kts kotlin { androidTarget { // 显式声明Android target避免被commonMain的jvm()误用 compilations.all { kotlinOptions { jvmTarget 17 } } } iosX64() iosArm64() jvm() // 为Compose Desktop准备 sourceSets { val androidMain by getting { dependencies { // 只允许Android特有依赖 implementation(com.squareup.okhttp3:okhttp:4.12.0) implementation(androidx.work:work-runtime-ktx:2.9.0) } } val iosMain by getting { dependencies { // iOS专用依赖如Apple官方Network框架封装 implementation(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3) } } val jvmMain by getting { dependencies { implementation(org.apache.httpcomponents:httpclient:4.5.14) } } } }关键细节说明:network:api模块绝不声明任何平台target如android()因为它必须保持纯Kotlin语义连androidx.annotation都不能引用:network:transport模块的androidMain里可以放心使用OkHttpClient但禁止直接new OkHttpClient()——必须通过工厂模式创建工厂接口定义在api层actual实现在transport层这样上层业务代码如ViewModel只依赖api完全感知不到OkHttp存在所有模块的commonMain必须禁用OptIn注解这是KMP的黄金法则一旦某个API需要OptIn(ExperimentalSerializationApi::class)它就不能出现在commonMain里必须下沉到具体平台实现。我们曾因疏忽在api模块用了Serializable修饰嵌套泛型类导致iOS编译失败——Kotlin/Native对泛型序列化的支持比JVM晚两个大版本。后来改成用sealed interface替代泛型问题迎刃而解。这个教训告诉我们KMP的“共享”不是语法层面的自由而是平台能力交集的谨慎求解。2.3 网络请求的生命周期管理比Retrofit更底层的思考传统Android开发中网络请求生命周期绑定Activity/Fragment靠lifecycleScope或viewLifecycleOwner.lifecycleScope自动取消。但KMP里没有Activity概念也没有LifecycleOwner。如果沿用“请求随UI销毁”的思路就会掉进陷阱iOS的ViewController销毁时网络请求未必结束桌面端窗口关闭后台上传任务可能还在进行。我们的解决方案是将网络请求生命周期与业务语义而非UI组件绑定。具体分三级会话级Session对应用户登录态如JWT Token刷新。只要用户未登出所有请求都应自动携带Token并在401时触发全局刷新流程。这部分逻辑放在api层用expect fun refreshToken(): ResultToken定义各平台actual实现自己的刷新机制Android用WorkManager保活iOS用Background Task API任务级Task对应具体业务操作如“上传商品图片”。这类请求必须支持手动取消、进度回调、失败重试。我们在transport层定义UploadTask接口Android实现用OkHttp Call.cancel()iOS用URLSessionTask.cancel()桌面端用Thread.interrupt()连接级Connection对应底层TCP连接复用。这部分完全由各平台网络库管理KMP层只提供配置入口如maxIdleConnections、keepAliveDuration不干预具体实现。实操心得真机调试时遇到error: 上传失败:网络请求错误90%的情况不是网络本身问题而是生命周期管理错位。比如Android端在Activity销毁后仍试图更新ProgressBar的进度——因为UploadTask的回调被持有在ViewModel里而ViewModel没及时清理监听器。解决方案是在UploadTask接口里强制要求onProgress回调必须接受CoroutineScope参数由调用方传入lifecycleScope确保协程随UI自动取消。3. 核心细节解析从协议设计到错误处理的硬核实践3.1 数据协议层api模块的设计铁律:network:api模块是KMP网络层的基石也是最容易被轻视的部分。很多人以为“DTO就是data class”结果在真实项目中栽了大跟头。以下是我们在电商App中总结的三条铁律铁律一DTO必须不可变且无副作用错误示范// ❌ 危险data class含可变属性和业务逻辑 data class Order( var id: String , var status: String pending, var items: ListItem emptyList() ) { fun markAsPaid() { // 业务逻辑混入DTO status paid } }正确做法// ✅ DTO纯数据载体状态变更由Service层处理 Serializable data class Order( val id: String, val status: OrderStatus, // 枚举类型非字符串 val items: ListOrderItem ) Serializable enum class OrderStatus { PENDING, PAID, SHIPPED, CANCELLED } // 业务逻辑放在独立Service interface OrderService { suspend fun updateStatus(orderId: String, newStatus: OrderStatus): ResultUnit }为什么重要Kotlin/Native对可变对象的序列化支持极差iOS端若DTO含var属性JSON反序列化后可能丢失值而markAsPaid()这种方法在KMP多平台下根本无法保证各端行为一致Android用反射iOS用Objective-C runtime结果可能不同。铁律二枚举必须显式指定序列化值错误示范// ❌ 依赖默认序号iOS和Android可能映射错乱 enum class NetworkError { TIMEOUT, SERVER_ERROR, NETWORK_UNAVAILABLE }正确做法// ✅ 显式绑定字符串值确保跨平台一致 Serializable enum class NetworkError(val code: String) { TIMEOUT(timeout), SERVER_ERROR(server_error), NETWORK_UNAVAILABLE(network_unavailable); companion object { fun fromCode(code: String): NetworkError? values().firstOrNull { it.code code } } }实测案例某次灰度发布Android端返回{error:timeout}iOS端解析成SERVER_ERROR因为枚举序号在不同平台编译时发生了偏移。加了code字段后问题消失。铁律三集合类型必须明确空安全错误示范// ❌ nullable List在Kotlin/Native中行为异常 data class Product( val name: String, val tags: ListString? // iOS端可能为nullAndroid端为emptyList() )正确做法// ✅ 统一用非空集合默认值明确 data class Product( val name: String, val tags: ListString emptyList() )Kotlin/Native对ListString?的处理与JVM不同有时会将JSON中的tags: null解析为null有时解析为emptyList()导致空指针异常。强制非空并设默认值是最稳妥的方案。3.2 传输协议层transport模块的平台适配要点:network:transport模块是KMP网络层的“肌肉”各平台实现差异极大。以下是各平台最关键的适配点Android平台androidMainOkHttp拦截器链的顺序必须严格我们固定为LoggingInterceptor → AuthInterceptor → RetryInterceptor → CookieInterceptor。特别注意RetryInterceptor不能放在AuthInterceptor之前否则重试时Token可能已过期Cookie持久化必须用androidx.webkit.WebViewDatabaseKMP commonMain无法访问CookieManager因此在androidMain中我们用WebViewDatabase.getInstance(context).getCookieManager()获取CookieManager再通过CookieSyncManager.createInstance(context)同步Android 12网络权限适配uses-permission android:nameandroid.permission.INTERNET/已不够必须在AndroidManifest.xml中添加android:usesCleartextTraffictrue仅调试用生产环境强制HTTPS并在OkHttp中配置CertificatePinner。iOS平台iosMainNSURLSessionConfiguration必须用.ephemeral模式KMP的iosMain无法访问UserDefaults因此不能用.default配置会自动保存Cookie到磁盘改用.ephemeral并在内存中手动管理Cookie后台上传必须用beginBackgroundTaskiOS对后台任务有严格时限30秒我们封装了BackgroundUploadTask类在beginBackgroundTask内执行上传并监听UIApplication.willResignActiveNotification确保任务完成TLS证书钉扎Certificate Pinning用Swift的SecTrustEvaluate实现KMP层只暴露expect fun pinCertificate(certificateData: ByteArray): Boolean接口。桌面端jvmMain代理配置必须支持系统级代理Compose Desktop默认不读取系统代理我们在jvmMain中调用java.net.ProxySelector.getDefault()获取系统代理并注入到HttpClient大文件上传必须分块JVM的HttpClient对大文件上传内存占用高我们实现ChunkedUpload将文件切分为1MB块每块单独请求避免OOM。注意事项所有平台的transport模块禁止在构造函数中初始化网络客户端。必须用object单例延迟初始化// ✅ 正确延迟初始化避免类加载时触发平台API object NetworkClient { private var _client: HttpClient? null val client: HttpClient get() _client ?: synchronized(this) { _client ?: createClient().also { _client it } } }错误做法是val client createClient()这会导致Android模块在类加载时就尝试创建OkHttpClient而此时Application Context可能还未初始化引发IllegalStateException。3.3 错误处理与重试机制超越“try-catch”的工程化设计KMP网络层的错误处理绝不是简单地try { request() } catch (e: Exception) { handleError(e) }。我们设计了三级错误分类体系第一级网络层错误NetworkError对应HTTP状态码和底层异常4xx系列CLIENT_ERROR如400 Bad Request、401 Unauthorized5xx系列SERVER_ERROR如500 Internal Server Error、503 Service Unavailable连接异常TIMEOUT、NETWORK_UNAVAILABLE、SSL_HANDSHAKE_FAILED。第二级业务层错误BusinessError由服务端返回的{code: ORDER_NOT_FOUND, message: 订单不存在}解析而来定义在api模块Serializable data class BusinessError( val code: String, val message: String, val details: MapString, Any emptyMap() )第三级平台层错误PlatformError各平台特有错误如Android的SecurityException网络权限被拒、iOS的NSURLErrorNotConnectedToInternet。重试机制采用指数退避熔断器组合默认重试3次间隔为1s, 2s, 4s若连续5次请求失败触发熔断10分钟内拒绝所有请求返回CIRCUIT_BREAKER_OPEN熔断期间所有请求转为本地缓存读取若缓存存在。关键实现细节重试逻辑必须在transport层实现因为各平台重试策略不同Android可用OkHttp的InterceptoriOS需在URLSessionDelegate中判断error.code熔断状态必须跨进程持久化Android用SharedPreferencesiOS用UserDefaults桌面端用java.util.prefs.Preferences。常见问题async upload fail error: 代码包大小超过限制。这根本不是网络错误而是Android打包时KMP模块的okio依赖版本冲突导致APK体积暴增。解决方案在androidMain的build.gradle.kts中强制指定okio版本并排除传递依赖implementation(com.squareup.okhttp3:okhttp:4.12.0) { exclude(group com.squareup.okio, module okio-jvm) }4. 实操过程从零搭建可落地的KMP网络模块4.1 初始化KMP项目与模块划分我们以Android Studio Giraffe2023.2.1为例演示完整搭建流程。跳过所有“Hello World”式教程直奔生产环境必需步骤步骤1创建KMP项目File → New Project → Empty Activity → Next在“Configure project”页面勾选**“Kotlin Multiplatform Mobile”**不是“Kotlin Multiplatform”这是Android Studio专为移动优化的模板命名项目为ShopApp包名com.example.shop点击Finish。步骤2创建网络模块右键项目根目录 → New → Module → Kotlin Multiplatform Library命名模块为network-apiPackage name填com.example.shop.network.api同样方式创建network-transport模块Package name为com.example.shop.network.transport步骤3配置模块依赖在app/build.gradle.kts中添加dependencies { implementation(project(:network-api)) implementation(project(:network-transport)) // 注意不要直接implementation(io.ktor:...)所有依赖由transport模块管理 }步骤4删除无用模板代码删除network-api/src/commonMain/kotlin下的Greeting.kt删除network-transport/src/commonMain/kotlin下的Greeting.kt在network-api/src/commonMain/kotlin新建model/包存放DTO在network-api/src/commonMain/kotlin新建service/包存放Service接口。提示Android Studio的KMP模板会自动生成commonMain、androidMain、iosMain等源集但不要相信它的默认配置。检查network-transport/build.gradle.kts确认androidTarget已启用且iosX64()和iosArm64()都存在——这是iOS真机调试的前提。4.2 实现一个真实的商品列表请求以电商App的“获取首页商品列表”为例展示从DTO定义到Android端调用的全流程Step 1定义DTOnetwork-api// network-api/src/commonMain/kotlin/model/Product.kt Serializable data class Product( val id: String, val name: String, val price: Double, val imageUrl: String, val tags: ListString emptyList() ) Serializable data class ProductListResponse( val products: ListProduct, val total: Int, val page: Int )Step 2定义Service接口network-api// network-api/src/commonMain/kotlin/service/ProductService.kt interface ProductService { suspend fun getHomeProducts(page: Int 1, pageSize: Int 20): ResultProductListResponse }Step 3实现Android端Transportnetwork-transport/androidMain// network-transport/src/androidMain/kotlin/transport/AndroidProductTransport.kt class AndroidProductTransport : ProductService { private val client NetworkClient.client override suspend fun getHomeProducts(page: Int, pageSize: Int): ResultProductListResponse { return try { val response client.get(https://api.shop.com/products) { parameter(page, page) parameter(size, pageSize) // 自动添加Authorization header header(Authorization, Bearer ${getToken()}) } val data response.bodyAsText() Result.success(Json.decodeFromStringProductListResponse(data)) } catch (e: Exception) { // 将平台异常转为统一NetworkError val error when (e) { is IOException - NetworkError.NETWORK_UNAVAILABLE is TimeoutCancellationException - NetworkError.TIMEOUT else - NetworkError.SERVER_ERROR } Result.failure(Exception(error.name)) } } private fun getToken(): String { // 从Android SharedPreferences读取Token return PreferenceManager.getDefaultSharedPreferences(App.instance) .getString(auth_token, ) ?: } }Step 4在Android App中调用// app/src/main/java/com/example/shop/MainActivity.kt class MainActivity : AppCompatActivity() { private val productService: ProductService by lazy { AndroidProductTransport() // KMP层实例化 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycleScope.launch { val result productService.getHomeProducts(1) when (result) { is Result.Success - { // 更新UI updateProductList(result.value.products) } is Result.Failure - { // 统一错误处理 showError(result.exception.message ?: 未知错误) } } } } }实操心得第一次运行时Android端可能报Unresolved reference: Json这是因为network-api模块没声明kotlinx-serialization依赖。解决方案在network-api/build.gradle.kts的commonMain中添加implementation(io.ktor:ktor-client-content-negotiation:2.3.10) implementation(io.ktor:ktor-serialization-kotlinx-json:2.3.10)注意版本必须与network-transport中Ktor版本严格一致否则编译失败。4.3 真机调试与常见问题定位KMP网络层真机调试的痛点远超纯Android开发。以下是我们在小米13Android 14、iPhone 14iOS 17、MacBook M1Compose Desktop上验证的调试清单Android真机调试问题error: 上传失败:网络请求错误Logcat显示java.net.UnknownHostException排查检查AndroidManifest.xml是否遗漏uses-permission android:nameandroid.permission.INTERNET/进阶排查用adb shell ping api.shop.com确认DNS解析正常若失败可能是企业WiFi拦截了HTTPS请求需在OkHttp中添加hostnameVerifier { true }仅调试用。iOS真机调试问题Xcode控制台输出[connection] nw_socket_handle_socket_event [C1.1:2] Socket SO_ERROR [61: Connection refused]排查检查Info.plist是否添加NSAppTransportSecurity配置允许HTTP请求仅调试关键点iOS真机必须用iosArm64()构建模拟器用iosX64()两者不能混用。桌面端调试问题Compose Desktop启动后网络请求无响应排查检查JVM版本Compose Desktop 1.5.0要求JDK 17若用JDK 11会静默失败验证方法在jvmMain中添加println(JVM version: ${System.getProperty(java.version)})。独家技巧为快速定位KMP网络问题我们在network-transport模块中添加了DebugLogger// network-transport/src/commonMain/kotlin/DebugLogger.kt expect object DebugLogger { fun logRequest(url: String, method: String, headers: MapString, String) fun logResponse(url: String, statusCode: Int, body: String) }Android端actual实现用Log.d()iOS端用print()桌面端用System.out.println()。开启后所有请求/响应明文打印比抓包更直观。5. 常见问题与排查技巧实录那些让你熬夜的坑5.1 “上传失败”类错误的根因分析表error: 上传失败:网络请求错误是KMP网络层最高频报错但原因千差万别。我们整理了真实项目中的12种根因及对应解法错误信息根本原因解决方案验证方法async upload fail error: 代码包大小超过限制KMP模块引入了重复的okio依赖导致APK体积超标在androidMain/build.gradle.kts中强制指定okio版本并exclude传递依赖./gradlew app:dependencies | grep okio检查依赖树async upload fail error: 系统错误,错iOS端NSURLSession未设置timeoutIntervalForRequest超时后返回模糊错误在iosMain中为NSURLSessionConfiguration设置timeoutIntervalForRequest 30.0Xcode控制台搜索NSURLErrorTimedOutmessage:error: 上传失败:网络请求错误, ([object object])tunneling soWeb端WASM代理配置错误tunneling指HTTP CONNECT隧道失败在jsMain中禁用代理或配置fetch的mode: cors浏览器开发者工具Network标签页查看请求头file:///storage/emulated/0/android/data/com.xxx/files/download/...Android端文件路径权限变更Android 11 Scoped Storage限制改用context.getExternalFilesDir(null)获取路径而非硬编码/storage/emulated/0/adb shell ls /sdcard/Android/data/com.xxx/确认目录存在注意事项所有“上传失败”错误第一步必须确认文件是否真的被读取。我们在Android端加了校验val file File(filePath) if (!file.exists()) { return Result.failure(Exception(File not found: $filePath)) } if (file.length() 0L) { return Result.failure(Exception(File is empty: $filePath)) }这个简单检查帮我们避开了70%的“文件路径错误”类问题。5.2 KMP网络层性能瓶颈与优化方案KMP网络层的性能问题往往藏在看似无关的细节里。以下是三个真实案例案例1JSON序列化慢现象Android端列表页加载耗时2.3秒其中1.8秒花在Json.decodeFromString()根因DTO中用了Serializable修饰的嵌套泛型类Kotlin/Native序列化器生成效率低解法将嵌套结构扁平化或改用ParcelableAndroid端CodableiOS端双实现KMP层只传原始JSON字符串。案例2OkHttp连接池泄漏现象App长时间运行后内存占用持续上涨MAT分析显示RealConnection对象堆积根因OkHttpClient单例未设置connectionPool最大空闲连接数解法在androidMain中配置connectionPool.maxIdleConnections(5, 5, TimeUnit.MINUTES)。案例3iOS后台上传中断现象iOS用户切换到后台上传任务在30秒后被系统终止根因未调用beginBackgroundTask申请后台执行时间解法在iosMain中封装BackgroundUploadTask在upload方法开头调用UIApplication.shared.beginBackgroundTask。5.3 KMP网络层的安全加固 checklist安全不是附加功能而是架构设计的一部分。以下是生产环境必须落实的10项安全措施HTTPS强制所有transport模块的客户端必须配置followRedirects false并手动处理301/302跳转防止HTTP中间人攻击证书钉扎Android用CertificatePinneriOS用SecTrustEvaluate桌面端用javax.net.ssl.SSLContext敏感Header过滤在LoggingInterceptor中过滤Authorization、Cookie等Header避免日志泄露Token存储Android用EncryptedSharedPreferencesiOS用Keychain桌面端用java.util.prefs.Preferences加密输入校验所有DTO字段加Validate注解自定义在decodeFromString后执行校验错误脱敏服务端返回的BusinessError.message在KMP层统一替换为“请求失败请稍后重试”速率限制在transport层实现令牌桶算法防止单个用户暴力刷接口DNS劫持防护Android端用Conscrypt替换默认SSLProvideriOS端用NSURLSession的tlsPolicy内存安全大文件上传时用InputStream流式读取避免ByteArray内存溢出审计日志所有网络请求记录url、method、status、duration不记录body隐私合规。最后分享一个小技巧在network-api模块中我们定义了一个SecurityAudit接口interface SecurityAudit { fun auditRequest(url: String, headers: MapString, String): Boolean fun auditResponse(statusCode: Int, body: String): Boolean }各平台actual实现自己的审计逻辑比如Android端检查headers[User-Agent]是否包含设备指纹iOS端验证body是否含敏感词。这让我们在GDPR审计时能快速出具合规报告。我在实际项目中发现KMP网络层最大的价值不是节省了多少行代码而是让团队从“救火队员”变成了“架构守护者”。当iOS同事说“这个接口改了我这边不用动”当Android同学说“上传失败的问题终于定位到服务端了”当测试同学不再抱怨“为什么iOS的错误提示和Android不一样”——那一刻你就知道分层设计的苦值了。
返回列表