曲径通幽 —— Android 息屏 TCP 连接管理

一、场景概述

设备与应用 :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)  

现象为网络中没有正常完成数据传输,被控设备未收到控制指令,控制无法生效


二、关键日志分析

  1. 系统网络层异常 通过 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 可判定为网络权限类问题

  1. 系统网络策略
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"
  1. 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... |
    | 链路层中断 | 虚假联网状态下写入触发 ENETUNREACHECONNABORTED(errno=103),应用层无重连逻辑,报错后沉默。

四、Socket 保活与系统特性说明

  1. TCP KeepAlive

Dart 代码示例:

dart 复制代码
RawSocketOption ka = RawSocketOption(
  SocketOption.levelSocket, SocketOption.optKeepAlive, 1);
socket.setOption(ka);
  1. 前台 Service & 忽略电池优化

原生申请:

kotlin 复制代码
if (!powerManager.isIgnoringBatteryOptimizations(pkg)) {
  startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
  Uri.parse("package:$pkg")))
  }
startForegroundService(...)
  1. 获得 Wi-Fi 锁
scss 复制代码
wifiManager.createWifiLock(WIFI_MODE_FULL_HIGH_PERF, "myLock").acquire()
  1. 神隐模式关闭

用户需在「设置→电源和性能→神隐模式」中排除 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

  1. Doze 模式下心跳仍偶尔成功;
  2. 路由重启后心跳发现失联并重连。

3. 前台服务 + 忽略电池优化

实现:在亮屏重连基础上,启动前台 Service,使 App 保持"可用"状态。

测试 Case

  1. 锁屏 30 min → 心跳不中断;
  2. Miui "神隐模式"开启时仍可通信(白名单已生效)。

4. WorkManager 周期唤醒

实现:使用 WorkManager 每隔 15 min 唤醒尝试重连。

测试 Case

  1. 在 Doze 深度保活窗口执行;
  2. 无需保活心跳,降低电量消耗。

六、方案对比与推荐

方案 优点 缺点 Flutter 兼容性
1. 息屏断开/亮屏重连 最简单、稳定绕过后台限制 息屏时无法控制;重连延迟 ⭐⭐⭐⭐⭐
2. 心跳+KeepAlive 保持长连接,自恢复能力强 Doze 下心跳不可靠;实现稍复杂 ⭐⭐⭐⭐
3. 前台 Service 最强保活,Doze 无感 用户体验成本;通知占用 ⭐⭐⭐
4. WorkManager 与系统兼容,省电 间隔不可低于15 min,不适合实时 ⭐⭐

推荐 :对于实时控制 场景,方案 1(息屏断开/亮屏重连)配合短心跳+重连最为合理。

  • 在 Flutter 层无需复杂原生改动;
  • 保证用户一亮屏即可恢复连接;
  • 与 Miui 系统兼容性最高。

七、总结

通过日志检索命令 快速定位 ENETUNREACH 与系统策略日志,结合 TCP 空闲回收、Android Doze 与 HyperOS "神隐模式"交互,确认息屏后台网络被系统挂起导致长连接假在线。

最优解是:

  1. 息屏时主动释放 TCP
  2. 亮屏时立即重连并恢复心跳
  3. 在必要时启用 TCP KeepAlive
    此策略实现成本低、可靠性高,且能在 Flutter 技术栈内完成,对绝大多数 Android 设备(尤其小米 HyperOS)均有效。
相关推荐
Bryce李小白2 小时前
Flutter中实现页面跳转功能
flutter
一笑的小酒馆4 小时前
Android12去掉剪贴板复制成功的Toast
android
一笑的小酒馆5 小时前
Android12App启动图标自适应
android
程序员江同学6 小时前
Kotlin 技术月报 | 2025 年 7 月
android·kotlin
某空m7 小时前
【Android】内容提供器
android
Greenland_128 小时前
Android 编译报错 Null extracted folder for artifact: xxx activity:1.8.0
android
ZhuYuxi3338 小时前
【Kotlin】const 修饰的编译期常量
android·开发语言·kotlin
Bryce李小白8 小时前
Kotlin 实现 MVVM 架构设计总结
android·开发语言·kotlin
Kiri霧8 小时前
Kotlin位运算
android·开发语言·kotlin
xjdkxnhcoskxbco8 小时前
kotlin基础【3】
android·开发语言·kotlin