Android 16 下 uni-app APP 提示"网络连接失败"的排障与修复
一次看起来很普通的网络故障,最后却和服务器、联网权限、HTTP 明文访问都没有关系。
在这次问题中,手机浏览器可以正常访问后端健康检查接口,并得到:
json
{
"status": "UP",
"groups": ["liveness", "readiness"]
}
H5 页面也能正常使用,但同一套前端代码打包为 APK、安装到手机后,请求验证码时却始终提示"网络连接失败"。
查看打包的APP的应用设置,发现也有流量数据,于是外卖需要考虑到的就不是没有打开APP的联网功能。

这个现象很容易让人继续排查服务器、防火墙、反向代理和 Android 联网权限,实际故障发生得更早:APK 内的旧版 DCloud 原生网络适配器在创建请求时就已经抛出异常,请求根本没有发到服务器。
本文记录完整的定位过程、真实原因和修复方法,供后续遇到类似问题时快速复用。
一、问题现象
当时的环境组合如下:
| 项目 | 问题环境 |
|---|---|
| 手机系统 | Android 16(API 36) |
APK targetSdkVersion |
36 |
| 本机 HBuilderX | 4.29,发布于 2024 年 |
@dcloudio/uni-* 依赖 |
3.0.0-4020420240722001 |
| 后端状态 | 已部署,健康检查正常 |
| H5 状态 | 可以正常访问后端 |
| APK 状态 | 验证码请求提示"网络连接失败" |
前端的统一请求封装会把底层网络异常转换成便于用户理解的提示,因此页面上只显示"网络连接失败"。这个提示描述的是最终表现,并不等于故障一定发生在网络连接阶段。
二、为什么最初的排查方向不对
1. 不是 Android 联网权限缺失
项目的 talk-ui/src/manifest.json 已声明:
xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
因此不是忘记声明 INTERNET 权限。
2. 不是 HTTP 明文访问被系统禁止
项目在 Android 配置中保留了:
json
{
"usesCleartextTraffic": true
}
这仍然是当前 HTTP 内测环境需要的配置,但它不是本次故障的原因。日志显示异常发生在 DCloud 网络适配器初始化 TLS 协议时,此时业务请求还没有被创建,更没有进入 HTTP 明文访问检查阶段。
3. 后端健康检查正常不代表 APK 请求栈正常
手机浏览器访问健康检查成功,只能证明下面这条链路可用:
text
手机网络 -> 浏览器网络栈 -> 公网入口 -> 后端服务
APK 发起请求时走的是另一条链路:
text
uni-app 页面 -> DCloud 原生网络适配器 -> Android 网络栈 -> 公网入口 -> 后端服务
两条链路共用同一个服务器,却不共用同一个客户端网络实现。H5 正常而 APP 异常时,不能只盯着后端,还要检查原生运行时和打包基座。
三、用 ADB 找到真正的异常
连接开启 USB 调试的手机后,先确认设备和 Android API 级别:
powershell
adb devices -l
adb shell getprop ro.build.version.release
adb shell getprop ro.build.version.sdk
再确认已安装 APK 的目标 SDK:
powershell
adb shell dumpsys package com.seekta.mobile |
Select-String "targetSdk"
复现前清空日志,然后在手机上重新点击获取验证码:
powershell
adb logcat -c
adb logcat |
Select-String "IllegalArgumentException|TLSv1|com.seekta.mobile"
最终捕获到的核心异常是:
text
java.lang.IllegalArgumentException: protocol TLSv1 is not supported
这条日志给出了两个关键信号:
- 异常发生在客户端创建请求之前,而不是服务器返回错误之后。
- 失败对象是 APK 内的原生网络层,不是 Vue 页面中的业务接口代码。
与此同时,服务器访问日志中没有对应的验证码请求。客户端异常和服务器无请求记录互相印证,说明请求确实没有离开手机。
四、真实原因:新系统运行了旧版原生网络适配器
旧 APK 的兼容链条是:
text
Android 16 / API 36
+
targetSdkVersion 36
+
HBuilderX 4.29 的旧版 App 原生运行时
+
2024 年 7 月版本的 @dcloudio/uni-* 编译依赖
↓
旧网络适配器尝试启用 TLSv1
↓
Android 运行环境拒绝该协议配置
↓
请求创建失败,前端显示"网络连接失败"
问题并不是服务器要求使用 TLSv1。实际情况是旧版 DCloud 网络适配器在初始化网络客户端时仍带有旧协议配置,而该配置在 Android 16、targetSdkVersion 36 的运行组合下不再被接受。
这也解释了为什么修改 API 地址、重复检查服务器、增加联网权限都没有效果:这些配置位于请求链条的后半段,而程序在原生网络客户端初始化阶段就已经失败了。
五、修复方案
修复的核心不是绕过 Android 的安全限制,也不是重新开启 TLSv1,而是升级 DCloud 工具链和 uni-app 依赖,让新 APK 使用与 Android 16 兼容的原生网络实现。
第一步:升级 HBuilderX
将用于发行 APK 的 HBuilderX 升级到 5.24。打包电脑上真正执行"原生 App 云打包"的 HBuilderX 版本尤其重要,因为最终 APK 的原生基座由打包环境决定。
DCloud 的云打包环境文档显示,HBuilderX 5.09 及以上版本已经采用面向 API 36 的新版 Android 编译环境,因此继续使用 2024 年的旧工具链并不适合验证 Android 16 兼容性。
升级后重新打开项目,不要继续使用旧 HBuilderX 生成的 APK 做验证。
第二步:统一升级 uni-app 依赖
提交 c2df5b61b0dd6ebd43c51fb9bd2e85fe706486d2 完成了这一步,提交信息为:
text
build(app): 升级 uni-app 依赖至 HBuilderX 5.24
该提交把 talk-ui/package.json 中 9 个 DCloud 依赖统一从:
text
3.0.0-4020420240722001
升级为:
text
3.0.0-5020420260812001
涉及的依赖包括:
text
@dcloudio/uni-app
@dcloudio/uni-app-plus
@dcloudio/uni-components
@dcloudio/uni-h5
@dcloudio/uni-mp-weixin
@dcloudio/uni-automator
@dcloudio/uni-cli-shared
@dcloudio/uni-stacktracey
@dcloudio/vite-plugin-uni
DCloud 相关包必须使用同一发行版本,不应只升级其中一两个包,否则编译器、运行时和插件之间可能产生新的版本不一致。
第三步:重新安装依赖并确认版本
进入前端项目目录:
powershell
Set-Location .\talk-ui
npm install
当前项目忽略了 package-lock.json,本地可能还残留旧锁文件,因此不要只根据锁文件判断实际版本。安装完成后直接检查已安装依赖:
powershell
npm ls `
@dcloudio/uni-app `
@dcloudio/uni-app-plus `
@dcloudio/vite-plugin-uni
输出中的 DCloud 版本应与 talk-ui/package.json 保持一致,不应继续出现 4020420240722001。
第四步:完成 APP 资源构建检查
powershell
npm run build:app
看到 DONE Build complete 说明前端 APP 资源编译通过。需要注意,这一步只生成待打包资源,并不会自动替换手机里 APK 的原生网络层。
第五步:使用 HBuilderX 5.24 重新云打包
在 HBuilderX 5.24 中重新执行:
text
发行 -> 原生 App-云打包 -> Android
打包时继续使用项目原有的以下身份配置:
- DCloud AppID;
- Android 包名;
- DCloud 云端证书或原有签名证书。
不要为了修复网络问题更换包名或签名证书,否则新 APK 可能无法覆盖安装旧版本。分发前还应递增 talk-ui/src/manifest.json 中的 versionCode。
真正包含新版原生网络适配器的是重新云打包生成的 APK。仅更新 H5、仅运行 npm run build:app,或者继续安装旧 APK,都无法验证这次原生层修复。
第六步:覆盖安装并复验
回到仓库根目录,使用新 APK 覆盖安装旧版本:
powershell
adb install -r .\talk-ui\dist\release\apk\新版本.apk
覆盖安装可以同时验证签名一致性和升级路径,也能保留原有本地数据。安装后重新打开 APP,依次检查:
- 登录页可以加载验证码;
- 刷新验证码可以正常发起新请求;
- 登录、注册及其他 API 可以访问;
- ADB 日志中不再出现
protocol TLSv1 is not supported; - 后端访问日志可以看到来自 APP 的实际请求。
六、修复后的验证闭环
一次完整验证不能只看"页面不再报错",建议同时检查四层证据:
| 验证层级 | 通过标准 |
|---|---|
| 前端页面 | 验证码正常展示,不再提示网络连接失败 |
| Android 日志 | 不再出现 TLSv1 协议初始化异常 |
| 服务端日志 | 可以看到验证码接口请求到达服务器 |
| 构建版本 | APK 来自 HBuilderX 5.24,DCloud 依赖为 5020420260812001 |
只有这四层同时成立,才能确认手机中运行的确实是修复后的新 APK,而不是浏览器缓存、H5 资源或旧安装包造成的假象。
需要说明的是,DCloud 官方资料用于确认打包环境、目标 SDK 和版本兼容配置;protocol TLSv1 is not supported 的具体根因判断来自本次 ADB 日志、服务器无请求记录以及升级后真机恢复正常这三项证据,并非引用自 DCloud 的故障公告。
七、以后遇到"APP 网络不可用"应该怎么查
这次问题最值得保留的不是某一条命令,而是排查顺序。
先判断请求有没有离开客户端
同时观察 ADB 日志和服务器访问日志。如果服务器完全收不到请求,就优先检查客户端原生层、DNS、证书、协议初始化和系统兼容性,不要急着修改后端业务代码。
对比 H5 与 APP 的差异
同一接口出现"H5 正常、APP 失败"时,重点检查:
- HBuilderX 和
@dcloudio/uni-*是否来自同一代版本; - APP 是否使用了过旧的原生运行时或自定义基座;
- Android 系统版本和
targetSdkVersion是否刚刚升级; - APK 是否确实重新云打包,而不是只更新了前端资源;
- 原生插件是否兼容当前云打包环境。
不要被统一错误文案限制思路
"网络连接失败"通常只是前端的兜底提示。排障时必须找到底层原始异常,尤其要关注:
text
IllegalArgumentException
SSLHandshakeException
UnknownHostException
ConnectException
Cleartext HTTP traffic not permitted
这些异常分别指向协议初始化、TLS 握手、DNS、连接建立和 HTTP 明文策略,处理方式完全不同。
八、结论
这次故障的最终结论可以概括为一句话:
Android 16 与
targetSdkVersion 36暴露了旧版 DCloud 原生网络适配器的 TLSv1 配置问题,导致请求在创建阶段失败;升级 HBuilderX 和整套@dcloudio/uni-*依赖,并重新云打包 APK 后恢复正常。
后端健康检查成功并没有错,Android 权限和 HTTP 明文配置也没有错。真正缺失的是"前端源码、uni-app 编译依赖、HBuilderX 打包环境、APK 原生基座、目标 Android 系统"这一整条版本兼容链的检查。
跨端项目中的"同一套代码"并不代表"同一套运行时"。遇到只有 APP 出现的网络问题时,原生基座版本应该和服务器地址一样,成为第一批核对项。