8、网络评分与切换:WifiNetworkScore、网络选择逻辑、自动切换触发条件

各位同学,今天我们来聊聊Wi-Fi服务里一个特别有意思的话题——网络评分与切换。

说实话,我刚接触这部分代码时,第一反应是:不就是信号弱了换个网吗?有什么好讲的?

后来被现实狠狠教育了一顿。有一次我在做某个定制项目,用户反馈手机在会议室里频繁断连。我查了半天,发现不是信号问题,而是评分机制把5GHz的弱信号评得比2.4GHz的强信号还高,导致手机死守着5GHz不放。嗯,从那以后,我再也不敢小看这个评分模块了。

8.1 网络评分:WifiNetworkScore 的底层逻辑

Wi-Fi网络评分,说白了就是给每个可见的网络打个分。分数高的优先连,分数低的靠边站。

Android里这个评分机制藏在 WifiNetworkScore 这个类里。它不是一个简单的RSSI映射,而是一个综合评估模型。

核心评分维度:

  • 信号强度(RSSI):基础分,-50dBm 比 -80dBm 高,这是常识
  • 频段偏好:5GHz 通常比 2.4GHz 加分,因为干扰少、速度快
  • 安全类型:WPA3 比 WEP 高,开放网络最低
  • 历史连接记录:之前连上后掉线过?扣分。连上后很稳定?加分
  • 当前负载:AP 接入设备太多?分数会降

我个人习惯把评分想象成一个「面试官」。面试官会看学历(信号强度)、工作经验(历史记录)、性格(安全类型)、甚至颜值(频段)。最后给个综合分,决定要不要录用。

来看一段核心代码,这是 WifiNetworkScore 的简化版:

// 来自 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNetworkScore.java
public class WifiNetworkScore {
    private int mRssiScore;          // 信号分
    private int mBandScore;          // 频段分
    private int mSecurityScore;      // 安全分
    private int mHistoryScore;       // 历史分
    private int mLoadScore;          // 负载分

    public int calculateTotalScore() {
        int total = 0;
        total += mRssiScore * 0.4;      // 信号权重 40%
        total += mBandScore * 0.25;     // 频段权重 25%
        total += mSecurityScore * 0.2;  // 安全权重 20%
        total += mHistoryScore * 0.1;   // 历史权重 10%
        total += mLoadScore * 0.05;     // 负载权重 5%
        return total;
    }
}

你可能会问:为什么信号权重占40%?其实这个比例不是固定的。不同厂商、不同Android版本都会调。我在某款平板项目里,因为用户经常在弱信号区域移动,就把信号权重提到了60%。效果立竿见影,切换更积极了。

避坑指南:我曾经遇到过一个坑——评分计算时没有考虑「同频干扰」。在密集部署的商场里,2.4GHz虽然信号满格,但实际吞吐量极低。后来我们在评分里加了一个「信道利用率」因子,才解决了这个问题。

8.2 网络选择逻辑:谁才是最优解?

评分算完了,接下来就是选谁的问题。Android的网络选择逻辑,我把它总结为三步走:

  1. 候选列表构建:把所有可见的、已保存的、安全的网络列出来
  2. 评分排序:按 WifiNetworkScore 从高到低排
  3. 择优录取:选分数最高的那个,发起连接

但这里有个细节——不是每次扫描都切换。Android有个「切换阈值」机制。简单说,新网络的分数必须比当前网络高出一定值,才会触发切换。这个阈值默认是10分(具体看厂商配置)。

为什么要设阈值?你想想看,如果每次信号波动都切换,手机就会变成「墙头草」,频繁断连重连,用户体验极差。

我参与过一个车载项目,车在移动中,Wi-Fi信号变化极快。我们不得不把阈值从10分降到5分,同时缩短扫描间隔。嗯,这里要注意,降低阈值会带来更频繁的切换,但车载场景下,用户更在意「能连上」,而不是「稳定」。场景决定策略。

网络选择的核心流程(伪代码):

// 网络选择逻辑简化版
List<ScanResult> candidates = getVisibleNetworks();
candidates = filterBySecurity(candidates);  // 过滤不安全网络
candidates = filterByHistory(candidates);   // 过滤黑名单网络

// 评分排序
candidates.sort((a, b) -> {
    int scoreA = WifiNetworkScore.calculate(a);
    int scoreB = WifiNetworkScore.calculate(b);
    return scoreB - scoreA;  // 降序
});

// 择优录取
ScanResult best = candidates.get(0);
if (best.score > currentNetwork.score + THRESHOLD) {
    connectTo(best);
}

这段代码看起来简单,但实际工程里要考虑的边界情况很多。比如:候选列表为空怎么办?当前网络分数为0怎么办?所有网络分数都低于阈值怎么办?

我个人习惯在候选列表为空时,保持当前连接不变,同时启动一个「紧急扫描」——缩短扫描周期,直到找到可用网络。

8.3 自动切换触发条件:什么时候该动?

自动切换不是随时发生的。Android定义了几个明确的触发条件:

触发条件 说明 典型场景
信号低于阈值 当前网络 RSSI 低于 -85dBm 走到信号死角
发现更优网络 新网络评分比当前高 10 分以上 进入另一个AP覆盖区
连接质量下降 丢包率超过 20% 或延迟超过 500ms AP 负载过高
用户主动触发 用户在设置里点击「切换网络」 手动选择

这里我想重点说说「连接质量下降」这个条件。很多开发者只关注信号强度,忽略了质量。我在一个视频会议项目里遇到过:信号满格,但视频卡成PPT。查了半天,发现是AP上接了太多设备,导致每个设备分到的带宽极低。

从那以后,我在评分模型里加入了「吞吐量探测」——定期发一个小包,测一下实际速度。如果速度低于预期,即使信号满格,也会触发切换。

注意:吞吐量探测不能太频繁。我见过有人每5秒测一次,结果手机CPU占用飙升,电池狂掉。建议间隔30秒以上,且只在信号良好但用户体验差时才触发。

还有一个容易被忽略的触发条件——「漫游」。在企业级网络里,多个AP组成一个ESSID。当手机从一个AP移动到另一个AP时,需要触发「漫游切换」。这个切换和普通切换不同,它不改变SSID,只改变BSSID。

Android的漫游逻辑在 WifiConnectivityManager 里。它会维护一个「候选BSSID列表」,按评分排序。当当前BSSID的评分低于某个阈值时,自动切换到列表里评分最高的BSSID。

我建议你在做漫游优化时,重点关注「切换时延」。从检测到信号变差,到完成新AP关联,这个时间越短越好。理想情况下,应该控制在200ms以内,否则用户会感觉到卡顿。

8.4 知识体系总览

为了让你更直观地理解整个网络评分与切换的体系,我画了一张图:

网络评分与切换知识体系 输入层 扫描结果 / 历史记录 评分引擎 WifiNetworkScore 选择逻辑 排序 + 阈值判断 信号强度 (40%) 频段偏好 (25%) 安全类型 (20%) 历史记录 (10%) 负载情况 (5%) 触发条件判断 信号低于阈值 发现更优网络 连接质量下降 用户主动触发 输出:执行网络切换

这张图从左到右展示了整个流程:输入层收集数据,评分引擎计算分数,选择逻辑决定是否切换,触发条件判断何时执行。每个环节都有对应的代码实现和配置参数。

最后说一句:网络评分与切换,看似是个小功能,但直接影响用户对「手机好不好用」的第一印象。我见过太多项目,因为切换逻辑没做好,被用户骂「Wi-Fi老是断」。所以,花时间把这块吃透,绝对值。


公众号:蓝海资料掘金营,微信deep3321