一、场景概述
设备与应用 :XIAOMI(HyperOS 12.X),安装 Flutter 应用通过 dart:io.Socket.connect(ip, port)
与局域网嵌入式设备建立 TCP 长连接后进行指令控制。
预期行为:在应用启动并连接设备后保持长连接,周期发送心跳;息屏/切后台后到切回前台唤醒无需重新建立连接,可稳定下发指令
实际问题:息屏一段时间后再亮屏即刻进行指令下发,下发指令失败,Flutter 日志出现大量:
log
SocketException: Software caused connection abort (errno = 103)
connect failed: ENETUNREACH (Network is unreachable)
现象为网络中没有正常完成数据传输,被控设备未收到控制指令,控制无法生效
二、关键日志分析
- 系统网络层异常 通过
adb logcat | grep "Software caused connection abort"
抓取网络触发系统日志(IP 、MAC 地址已处理,日志主要关注异常语句)
log
java.net.ConnectException: failed to connect to /3234:dv32:yyi:32::1 (port 156) from /:: (port 0) after 5000ms:
connect failed: ENETUNREACH (Network is unreachable)
通过 Network is unreachable
可判定为网络权限类问题
- 系统网络策略
yaml
I/JoyoseCloudControlManager3: network metered changed ... try to setNetworkAccessEnabled: false
I/MiuiNetworkPolicy: updateUidState uid=10281, uidState=2
try to setNetworkAccessEnabled
应用层有尝试打开网络的动作,但是失败了
grep 命令:
shell
db logcat | grep "JoyoseCloudControlManager3"
adb logcat | grep "MiuiNetworkPolicy"
- Wi-Fi 状态监测
makefile
I/wificond: station_bandwidth: ?
D/NetworkAccelerateSwitchService: getAverageWifiRssi:60
grep 命令:
shell
adb logcat | grep "wificond"
adb logcat | grep "NetworkAccelerateSwitchService"
三、原因定位
维度 | 原因说明 |
---|---|
TCP 空闲回收 | Dart 默认无底层 TCP KeepAlive,心跳在后台/Doze 模式下被暂停,导致路由器或系统内核回收长连接(ARP、NAT 超时)。 |
Android 省电策略 | Doze 模式 & Oreo 后台执行限制挂起应用网络;Wi-Fi 休眠(需要 WifiManager 锁或系统设置"睡眠时保持 Wi-Fi")。 |
HyperOS 神隐模式 | 小米"神隐模式"在息屏 4 min 后自动禁止后台联网,请参考 MIUI 文档: |
- "神隐模式"设置指南:c.mi.com/thread-...
- 电源性能管理:dev.mi.com/.../power-man... |
| 链路层中断 | 虚假联网状态下写入触发ENETUNREACH
或ECONNABORTED
(errno=103),应用层无重连逻辑,报错后沉默。
四、Socket 保活与系统特性说明
- TCP KeepAlive
Dart 代码示例:
dart
RawSocketOption ka = RawSocketOption(
SocketOption.levelSocket, SocketOption.optKeepAlive, 1);
socket.setOption(ka);
- 前台 Service & 忽略电池优化
原生申请:
kotlin
if (!powerManager.isIgnoringBatteryOptimizations(pkg)) {
startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:$pkg")))
}
startForegroundService(...)
- 获得 Wi-Fi 锁
scss
wifiManager.createWifiLock(WIFI_MODE_FULL_HIGH_PERF, "myLock").acquire()
- 神隐模式关闭
用户需在「设置→电源和性能→神隐模式」中排除 App;或引导用户手动操作。
五、解决策略
1. 息屏主动断开 + 亮屏重连
实现:
scss
```
@override
void didChangeAppLifecycleState(state) {
if (state==AppLifecycleState.paused) {
socket?.destroy(); heartbeat.cancel();
} else if (state==AppLifecycleState.resumed) {
_reconnectWithRetry();
}
}
```
测试 Case:
markdown
1. 息屏 10 min → 亮屏 → 1s 内自动重连成功;
1. 网络中断后重连失败重试 3 次;
1. 快速切后台/前台循环 5 次无内存泄露。
2. 心跳优化 + TCP KeepAlive
实现:10 s → 5 s 心跳,启用 TCP KeepAlive,底层周期 60 s。
测试 Case:
- Doze 模式下心跳仍偶尔成功;
- 路由重启后心跳发现失联并重连。
3. 前台服务 + 忽略电池优化
实现:在亮屏重连基础上,启动前台 Service,使 App 保持"可用"状态。
测试 Case:
- 锁屏 30 min → 心跳不中断;
- Miui "神隐模式"开启时仍可通信(白名单已生效)。
4. WorkManager 周期唤醒
实现:使用 WorkManager 每隔 15 min 唤醒尝试重连。
测试 Case:
- 在 Doze 深度保活窗口执行;
- 无需保活心跳,降低电量消耗。
六、方案对比与推荐
方案 | 优点 | 缺点 | Flutter 兼容性 |
---|---|---|---|
1. 息屏断开/亮屏重连 | 最简单、稳定绕过后台限制 | 息屏时无法控制;重连延迟 | ⭐⭐⭐⭐⭐ |
2. 心跳+KeepAlive | 保持长连接,自恢复能力强 | Doze 下心跳不可靠;实现稍复杂 | ⭐⭐⭐⭐ |
3. 前台 Service | 最强保活,Doze 无感 | 用户体验成本;通知占用 | ⭐⭐⭐ |
4. WorkManager | 与系统兼容,省电 | 间隔不可低于15 min,不适合实时 | ⭐⭐ |
推荐 :对于实时控制 场景,方案 1(息屏断开/亮屏重连)配合短心跳+重连最为合理。
- 在 Flutter 层无需复杂原生改动;
- 保证用户一亮屏即可恢复连接;
- 与 Miui 系统兼容性最高。
七、总结
通过日志检索命令 快速定位 ENETUNREACH
与系统策略日志,结合 TCP 空闲回收、Android Doze 与 HyperOS "神隐模式"交互,确认息屏后台网络被系统挂起导致长连接假在线。
最优解是:
- 息屏时主动释放 TCP
- 亮屏时立即重连并恢复心跳
- 在必要时启用 TCP KeepAlive 。
此策略实现成本低、可靠性高,且能在 Flutter 技术栈内完成,对绝大多数 Android 设备(尤其小米 HyperOS)均有效。