Android R Framework Wi-Fi 扫描机制解读

本文详细解析了Android系统中WifiConnectivityManager的三种ScanListener:AllSingleScanListener、SingleScanListener和PnoScanListener的工作原理。讨论了全频段扫描、周期扫描的时间间隔设置、PNO扫描以及不同状态下的网络扫描策略。同时,介绍了扫描失败后的重试机制以及如何处理扫描结果,特别是当PNO扫描发现有效网络时的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本文建立在研读 WifiConnectivityManager.java 和 其打印的log 的基础上

WifiConnectivityManager 内置三种 ScanListener,分别是

  • AllSingleScanListener implements WifiScanner.ScanListener
  • SingleScanListener implements WifiScanner.ScanListener
  • PnoScanListener implements WifiScanner.PnoScanListener
WifiScanner.ScanListener && WifiScanner.PnoScanListener
/**
 * interface to get scan events on; specify this on {@link #startBackgroundScan} or
 * {@link #startScan}
 */
// 说明 ScanListener 需要在 mScanner.startScan 或者 mScanner.startBackgroundScan 中被实例化
public interface ScanListener extends ActionListener {
    @Deprecated
    public void onPeriodChanged(int periodInMs);

    public void onResults(ScanData[] results);

    public void onFullResult(ScanResult fullScanResult);
}

/**
 * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
 * {@link #startConnectedPnoScan}.
 * {@hide}
 */
// 说明 PnoScanListener 需要在 mScanner.startDisconnectedPnoScan 或者 mScanner.startConnectedPnoScan 中被实例化
public interface PnoScanListener extends ScanListener {
    void onPnoNetworkFound(ScanResult[] results);
}
AllSingleScanListener

只需要看它对 onResults(WifiScanner.ScanData[] results) 的实现即可

  1. 通过 WifiScanner.isFullBandScan(results[0].getBandScanned(), true) 判断 isFullBandScanResults
    阅读相关代码可知,只要扫描了2.4G和5G频段,就认为返回 true

  2. 判断 mWaitForFullBandScanResults ,如真,说明需要等待全频段扫描结果
    判断上面得到的 isFullBandScanResults ,如假,说明本次扫描结果不是全频段扫描得到的,直接 return 掉
    注意, mWaitForFullBandScanResults 只会在 forceConnectivityScan 中被置 true

  3. 更新 WifiMetrics 相关信息

  4. 调用 handleScanResults 实际处理扫描结果,返回值赋值到 wasConnectAttempted 中,标识是否有网络被 WNS 选中

  5. 如果 Pno 扫描已开始,则更新 WifiMetrics 相关信息

  6. 判断 mInitialScanState , 分情况做处理
    mInitialScanState 可能的取值有

// Initial scan state, used to manage performing partial scans in initial scans
// Initial scans are the first scan after enabling Wifi or turning on screen when disconnected
private static final int INITIAL_SCAN_STATE_START = 0;
private static final int INITIAL_SCAN_STATE_AWAITING_RESPONSE = 1;
private static final int INITIAL_SCAN_STATE_COMPLETE = 2;

initial scan 为打开 wifi 或者无连接状态下亮屏进行的扫描
这三个值用来跟踪 initial scan 进行的状态

在第6步的判断中
如果 initial scan 尚处于 等待回复 状态,则将状态改成 已完成
如果此时有网络被 WNS 选中,那么执行 schedulePeriodicScanTimer ,否则执行 startConnectivityScan
这里的处理是合理的,如果这次扫描触发了连接请求,那么就开启周期扫描

schedulePeriodicScanTimer(getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex))

// Set up periodic scan timer
private void schedulePeriodicScanTimer(int intervalMs) {
    localLog("schedulePeriodicScanTimer, intervalMs: " + intervalMs);
    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        mClock.getElapsedSinceBootMillis() + intervalMs,
                        PERIODIC_SCAN_TIMER_TAG,
                        mPeriodicScanTimerListener, mEventHandler);
    mPeriodicScanTimerSet = true;
}

private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener =
        new AlarmManager.OnAlarmListener() {
            public void onAlarm() {
                periodicScanTimerHandler();
            }
        };

// Periodic scan timer handler
private void periodicScanTimerHandler() {
    localLog("periodicScanTimerHandler");
    // Schedule the next timer and start a single scan if screen is on.
    if (mScreenOn) {
        startPeriodicSingleScan();
    }
}

由此可见,周期扫描只在亮屏时进行

我们再看 getScheduledSingleScanIntervalMs 方法
这部分内容被我师傅魔改了,我照着解读吧

// 距离上次灭屏或者断连 3 min内,返回 5s
// 距离上次灭屏或者断连 3 ~ 10 min内,返回 10s
long currentTimeStamp = mClock.getElapsedSinceBootMillis();
boolean qucikScanProcess = (currentTimeStamp - mScreenOnTimeStamp) < QUICK_SCAN_TIME
    || (currentTimeStamp - mDisconnectTimeStamp) < QUICK_SCAN_TIME;
boolean qucikScanProcessSecond = (currentTimeStamp - mScreenOnTimeStamp) < QUICK_SCAN_TIME_LONG
    || (currentTimeStamp - mDisconnectTimeStamp) < QUICK_SCAN_TIME_LONG;
if(qucikScanProcess){
    mCurrentSingleScanScheduleIndex = 0;
    return QUICK_PERIODIC_SCAN_INTERVAL_MS;
}else if(qucikScanProcessSecond){
    mCurrentSingleScanScheduleIndex = 0;
    return QUICK_PERIODIC_SCAN_INTERVAL_MS_LONG;
}

// mCurrentSingleScanScheduleSec 是一个 存周期扫描时间的 int 数组
// mCurrentSingleScanScheduleSec 为空的话 取用默认配置,也就是 20s
// private static final int[] DEFAULT_SCANNING_SCHEDULE_SEC = {20, 40, 80, 160};
if (mCurrentSingleScanScheduleSec == null) {
    Log.e(TAG, "Invalid attempt to get schedule interval, Schedule array is null ");
    // Use a default value
    return DEFAULT_SCANNING_SCHEDULE_SEC[0] * 1000;
}

// 根据传参 index 取值
if (index >= mCurrentSingleScanScheduleSec.length) {
    index = mCurrentSingleScanScheduleSec.length - 1;
}
return mCurrentSingleScanScheduleSec[index] * 1000;

这里顺便再看看 setSingleScanningSchedule 的调用

setSingleScanningSchedule(mDisconnectedSingleScanScheduleSec);
setSingleScanningSchedule(mConnectedSingleSavedNetworkSingleScanScheduleSec);
setSingleScanningSchedule(mConnectedSingleScanScheduleSec);
setSingleScanningSchedule(null);
// 由此可见,源码根据连接状态准备了多种 SingleScanScheduleSec
// 但是我师傅的改法导致这些至少在 10mins 后才会被用到
// 无论如何,简单看下这些 SingleScanScheduleSec
// mDisconnectedSingleScanScheduleSec  -> [20, 40, 80, 160]
// mConnectedSingleSavedNetworkSingleScanScheduleSec -> [20, 40, 80, 160]
// mConnectedSingleScanScheduleSec -> [20, 40, 80, 160]
// 额,无语了,分析出来是这种配置

接下来看 startPeriodicSingleScan 方法的实现

  • 距离上次扫描未过去规定时间,则跳过本次扫描,触发差距时间的 timer

  • 检查以下情况以跳过这次扫描或者只做部分扫描

    • 网络够用
    • 连接极好,网络使用情况可接受且距离上次WNS选网只过去很小的一段时间
    • 有活跃的网络流,那么就不应该进行扫描(扫描会导致数据收发业务的中断)
  • 如果 firmware 支持漫游,那么就不进行扫描,否则进行部分频段的扫描

  • 如果需要扫描

    • 当前无连接且 initial scan 已开始时,则执行 startSingleScan 部分频段扫描
    • 普通执行 startSingleScan,是否需要进行全频段扫描根据 firmware 是否支持漫游来决定
    • 执行 schedulePeriodicScanTimer 方法
    • mCurrentSingleScanScheduleIndex++
  • 如果不需要扫描,执行 schedulePeriodicScanTimer 方法

我们再看 startSingleScan(实际上是 startForcedSingleScan) 方法的实现

  1. 填充 ScanSettings

  2. 通过 mScanner.startScan 触发 singleScanListener

SingleScanListener

根据注释,single scan 只在 DisconnectedPNO scan 发现有效网络 或者 被 watchdog timer 触发
此处存疑

SingleScanListener 的 onFailure 实现

  1. 重新计划扫描
    mSingleScanRestartCount 记录 single scan 重试次数,如果小于预设的 MAX_SCAN_RESTART_ALLOWED
    则执行 scheduleDelayedSingleScan 方法

  2. 如果已经超过了最大重试次数,则将 mSingleScanRestartCount 归零

scheduleDelayedSingleScan 方法实际上起了一个 RestartSingleScanListener 进行 startSingleScan 操作

PnoScanListener

PNO 扫描在灭屏后进行

PnoScanListener 的 onFailure 实现

  1. 重新计划扫描
    mScanRestartCount 记录 PNO scan 重试次数,如果小于预设的 MAX_SCAN_RESTART_ALLOWED
    则执行 scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS) 方法

  2. 如果已经超过了最大重试次数,则将 mScanRestartCount 归零

重点看 onPnoNetworkFound(ScanResult[] results) 的实现,该方法在 PNO 扫描发现有效网路时被调用
这里先摆出一个很重要的值,该值表示 PNO 扫描到的网络因信号太差被WNS拒绝后,PNO scan指数退避的时间

private int mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
  1. 遍历扫描结果,如果 informationElements 为空,跳过,否则添加到 mScanDetails 中

  2. 调用 handleScanResults 得到 WNS 选择结果, 赋值到 wasConnectAttempted 中

  3. WNS 未选中,如果 mLowRssiNetworkRetryDelay 大于 LOW_RSSI_NETWORK_RETRY_START_DELAY_MS ,则重置;执行 scheduleDelayedConnectivityScan 方法,将 mLowRssiNetworkRetryDelay * 2

  4. WNS 选中,则重置 mLowRssiNetworkRetryDelay

scheduleDelayedConnectivityScan 方法实际上起了一个 RestartSingleScanListener 进行 startSingleScan 操作

这里看下 startConnectivityScan 方法

  1. 停止正在进行的 Connectivity Scan

  2. 如果是亮屏,触发周期扫描

  3. 如果是灭屏无连接状态,触发PNO扫描 startDisconnectedPnoScan()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值