1. 项目背景与核心痛点
在移动应用开发中,WiFi扫描与界面流畅度的矛盾一直是个经典难题。传统同步扫描方式会导致UI线程阻塞,用户能明显感知到列表滚动卡顿、页面切换延迟等问题。我去年接手的一个智能家居控制App就深受其害——当用户在设备列表和WiFi设置页之间频繁切换时,平均帧率会从60fps暴跌至20fps以下。
这种卡顿的本质在于:WifiManager.startScan()是一个同步阻塞调用,在扫描完成前会冻结UI线程。更糟的是,Android 8.0之后每次扫描还需至少2秒冷却时间。当用户快速切换标签页时,积压的扫描请求会使情况雪上加霜。
2. 异步扫描架构设计
2.1 核心方案选型
我们最终采用"事件总线+状态机"的异步架构,关键组件包括:
- ScanScheduler:负责管理扫描间隔和触发条件
- WifiScanner:实际执行扫描的Service组件
- ResultCache:带时间戳的扫描结果缓存池
- StateMachine:处理并发扫描请求的状态控制
java复制// 伪代码示例:核心状态转换逻辑
enum ScanState {
IDLE,
SCANNING,
COOLING_DOWN
}
fun handleEvent(event: Event) {
when(currentState) {
IDLE -> if (event == UI_VISIBLE) startScan()
SCANNING -> if (event == SCAN_TIMEOUT) forceStopScan()
COOLING_DOWN -> if (event == COOLDOWN_END) resetState()
}
}
2.2 消息传递机制
采用EventBus而非接口回调,主要考虑三点:
- 避免回调地狱导致的代码混乱
- 支持多组件同时订阅扫描结果
- 方便在主线程和Worker线程间切换上下文
kotlin复制// 定义扫描结果事件
data class ScanResultEvent(
val networks: List<ScanResult>,
val fromCache: Boolean
)
// UI层订阅事件
@Subscribe(threadMode = MAIN)
fun onScanResult(event: ScanResultEvent) {
if (event.fromCache) showCachedBadge()
updateWifiList(event.networks)
}
3. 关键实现细节
3.1 缓存策略优化
单纯缓存扫描结果会导致显示过时信息。我们的解决方案是:
- 给每个AP添加RSSI衰减计数器
- 采用加权移动平均算法处理信号强度
- 对5GHz频段AP设置更短的缓存有效期
python复制# 信号强度计算伪代码
def calculate_quality(rssi, last_seen):
time_decay = max(0, 1 - (now() - last_seen)/300)
freq_factor = 1.2 if is_5ghz() else 1.0
return rssi * time_decay * freq_factor
3.2 界面更新优化
即使采用异步扫描,频繁更新RecyclerView仍可能导致卡顿。我们通过三项措施解决:
- 差异比对算法:使用DiffUtil只更新变化的AP项
- 节流机制:确保每秒最多刷新3次列表
- 预加载占位符:扫描期间显示骨架屏
java复制// 使用AsyncListDiffer处理更新
AsyncListDiffer<WifiEntry> differ = new AsyncListDiffer<>(
this,
new WifiEntryDiffCallback()
);
void submitNewList(List<WifiEntry> newList) {
differ.submitList(newList); // 自动计算差异
}
4. 性能对比测试
在Pixel 6 Pro上进行的基准测试显示:
| 场景 | 同步扫描帧率 | 异步扫描帧率 | 提升幅度 |
|---|---|---|---|
| 快速切换标签页 | 18fps | 57fps | 216% |
| 后台持续扫描 | 22fps | 60fps | 172% |
| 冷启动首屏 | 14fps | 38fps | 171% |
注意:测试时关闭了系统动画,数据来自Perfetto抓取的120帧采样
5. 踩坑实录与解决方案
5.1 冷启动白屏问题
初期方案在App启动时直接异步扫描,导致首屏加载后WiFi列表为空。最终采用双阶段加载:
- 先显示最近3次的历史缓存
- 异步触发新扫描并在完成后增量更新
5.2 多进程冲突
当App存在多个进程时,扫描请求会相互干扰。通过添加进程锁解决:
kotlin复制val lock = File(context.cacheDir, "wifi_scan.lock").channel
try {
lock.tryLock().use {
// 执行扫描操作
}
} catch (e: OverlappingFileLockException) {
Log.w("Scan already running in another process")
}
5.3 厂商定制ROM兼容
某些厂商ROM会修改WiFi扫描行为,需要特殊处理:
- 华为EMUI:需添加
android.permission.ACCESS_COARSE_LOCATION - 小米MIUI:需在设置中开启"始终允许扫描"
- 三星OneUI:需禁用WIFI_SCAN_THROTTLE特性
6. 进阶优化方向
对于需要更高性能的场景,还可以考虑:
- 预测性预加载:基于用户行为分析预触发扫描
- 分级缓存:根据AP信号强度采用不同缓存策略
- 硬件加速:利用WiFi芯片的硬件扫描特性
c复制// 底层可通过WifiNative接口优化
wifi_scan_cmd cmd = {
.scan_type = FAST_SCAN,
.num_probes = 3,
.scan_interval = 100
};
wifi_send_scan_command(iface, &cmd);
在实际项目中,这套方案使我们的WiFi设置页平均渲染时间从320ms降至90ms,用户投诉率下降72%。最关键的是培养了团队对UI性能的敏感度——现在所有后台任务都会自动带上@WorkerThread注解检查。
