曲径通幽 —— 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)均有效。
相关推荐
幻雨様5 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端6 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.7 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton8 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw12 小时前
安卓图片性能优化技巧
android
风往哪边走12 小时前
自定义底部筛选弹框
android
江上清风山间明月12 小时前
Flutter AlwaysScrollableScrollPhysics详解
flutter·滚动·scrollable·scrollphysics
iナナ12 小时前
传输层协议——UDP和TCP
网络·网络协议·tcp/ip·udp
Yyyy48213 小时前
MyCAT基础概念
android
Android轮子哥13 小时前
尝试解决 Android 适配最后一公里
android