【Android-WIFI】(四) Roaming机制Framework层源码分析

//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void handleScanResults(@NonNull List<ScanDetail> scanDetails,
        @NonNull String listenerName,
        boolean isFullScan,
        @NonNull HandleScanResultsListener handleScanResultsListener) {
    //....
    
    List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan(
            scanDetails, bssidBlocklist, cmmStates, mUntrustedConnectionAllowed,
            mOemPaidConnectionAllowed, mOemPrivateConnectionAllowed);
    mLatestCandidates = candidates;
    mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis();
    //...
    
    if ((mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)
            && mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) {
        // Split the candidates based on whether they are oem paid/oem private or not.
        Map<Boolean, List<WifiCandidates.Candidate>> candidatesPartitioned =
                candidates.stream()
                        .collect(Collectors.groupingBy(c -> c.isOemPaid() || c.isOemPrivate()));
        List<WifiCandidates.Candidate> primaryCmmCandidates =
                candidatesPartitioned.getOrDefault(false, Collections.emptyList());
        List<WifiCandidates.Candidate> secondaryCmmCandidates =
                candidatesPartitioned.getOrDefault(true, Collections.emptyList());
        List<WifiCandidates.Candidate> secondaryCmmCandidates_filtered =
                getSecondaryCandidatesFiltered(secondaryCmmCandidates);
        // Some oem paid/private suggestions found, use secondary cmm flow.
        if (!secondaryCmmCandidates.isEmpty()) {
            handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
                    listenerName, primaryCmmCandidates, secondaryCmmCandidates_filtered,
                    handleScanResultsListener);
            return;
        }
        // intentional fallthrough: No oem paid/private suggestions, fallback to legacy flow.
    }
    handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
            listenerName, candidates, handleScanResultsListener);
}

扫描结果可连接,回调 handleScanResults。handleScanResult 中首先进行一下细节处理,接着通过WifiNetworkSelector 来获取扫描结果中可连接的候选网络,在获取候选网络时,会对目前每一个网络连接结合扫描结果进行判断,如果扫描结果 SSID 和当前连接的网络的 SSID 不同,则可放入候选。如果所有扫描结果和当前的网络连接都是同一个 SSID,则需要根据 RSSI 判断信号强度是否足够强,足够强的话就不需要,进行网络选择。

(选择候选网络这里,到底是如何判断需要重新连接网络,是否是根据当前网络的强度还是什么,源码中选择候选网络时调用 isNetworkSelectionNeeded 判断是否需要挑选候选网络,只要搜索到有跟当前网络 SSID 不同的网络就会需要 select,而只有所有搜到的 SSID 都和当前网络相同的时候,这时候才会判断当前的网络强度是否有需要去切换网络,很困惑。)

以上过程中会成功获取 候选网络,接着处理这些候选网络并连接。分为两种情况,存在 Secondary Network 和不存在。我们看其中一种:handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable。

//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
        @NonNull String listenerName, @NonNull List<WifiCandidates.Candidate> candidates,
        @NonNull HandleScanResultsListener handleScanResultsListener) {
    WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates);
    if (candidate != null) {
        localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
        connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
        handleScanResultsWithCandidate(handleScanResultsListener);
    } else {
        localLog(listenerName + ":  No candidate");
        handleScanResultsWithNoCandidate(handleScanResultsListener);
    }
}

以 handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable 为例,首先从 candidates 中选出最合适的一个候选网络,接下来看看连接到该网络

//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void connectToNetworkUsingCmm(@NonNull ClientModeManager clientModeManager,
        @NonNull WifiConfiguration targetNetwork,
        @NonNull ConnectHandler connectHandler) {
    //...
    WifiConfiguration currentNetwork = coalesce(
            clientModeManager.getConnectedWifiConfiguration(),
            clientModeManager.getConnectingWifiConfiguration());
    String currentBssid = coalesce(
            clientModeManager.getConnectedBssid(), clientModeManager.getConnectingBssid());
    String currentAssociationId = getAssociationId(currentNetwork, currentBssid);

    // Already on desired network id, we need to trigger roam since the device does not
    // support firmware roaming (already checked in
    // isClientModeManagerConnectedOrConnectingToCandidate()).
    if (currentNetwork != null
            && (currentNetwork.networkId == targetNetwork.networkId
            || (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
            && currentNetwork.isLinked(targetNetwork)))) {
        localLog("connectToNetwork(" + clientModeManager + "): Roam to " + targetAssociationId
                + " from " + currentAssociationId);
        connectHandler.triggerRoamWhenConnected(currentNetwork, targetNetwork, targetBssid);
        return;
    }

    //...
}

两个 handle 函数继续向下都调用到 connectToNetworkUsingCmm,在满足如上代码中的条件判断:

if (currentNetwork != null
            && (currentNetwork.networkId == targetNetwork.networkId
            || (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
            && currentNetwork.isLinked(targetNetwork))))

这种情况下会执行到 ConnectHandler 的 triggerRoamWhenConnected,在其中开始WiFi漫游操作。一直向下调用到 ClientModeImpl.startRoamToNetwork,向自身状态机发送一个 CMD_START_ROAM 消息。在 L3ConnectedState 状态下处理这个消息

//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L3ConnectedState.class
public boolean processMessage(Message message) {
    switch (message.what) {
        //...
        case CMD_START_ROAM: {
            /* Connect command coming from auto-join */
            int netId = message.arg1;
            String bssid = (String) message.obj;
            if (bssid == null) {
                bssid = SUPPLICANT_BSSID_ANY;
            }
            WifiConfiguration config =
                    mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
            if (config == null) {
                loge("CMD_START_ROAM and no config, bail out...");
                break;
            }
            mLastScanRssi = mWifiConfigManager.findScanRssi(netId,
                    mWifiHealthMonitor.getScanRssiValidTimeMs());
            mWifiScoreCard.noteConnectionAttempt(mWifiInfo, mLastScanRssi, config.SSID);
            setTargetBssid(config, bssid);
            mTargetNetworkId = netId;

            logd("CMD_START_ROAM sup state "
                    + " my state " + getCurrentState().getName()
                    + " nid=" + Integer.toString(netId)
                    + " config " + config.getProfileKey()
                    + " targetRoamBSSID " + mTargetBssid);

            reportConnectionAttemptStart(config, mTargetBssid,
                    WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
            if (mWifiNative.roamToNetwork(mInterfaceName, config)) {
                mTargetWifiConfiguration = config;
                mIsAutoRoaming = true;
                mWifiMetrics.logStaEvent(
                        mInterfaceName, StaEvent.TYPE_CMD_START_ROAM, config);
                transitionTo(mRoamingState);
            } else {
                loge("CMD_START_ROAM Failed to start roaming to network " + config);
                reportConnectionAttemptEnd(
                        WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
                        WifiMetricsProto.ConnectionEvent.HLF_NONE,
                        WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                break;
            }
            break;
        }
        //...
    }
}

从message中获取netId和bssid的值。

通过netId从mWifiConfigManager获取对应的WifiConfiguration对象。

设置目标BSSID为config中的值,并将目标网络ID设置为netId。

如果调用mWifiNative.roamToNetwork方法成功,则进行以下操作:

  • 将mTargetWifiConfiguration设置为config。
  • 将mIsAutoRoaming设置为true。
  • 记录StaEvent.TYPE_CMD_START_ROAM类型的事件日志。
  • 进入mRoamingState状态。
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration config) {
    synchronized (mLock) {
        if (updateOnLinkedNetworkRoaming(ifaceName, config.networkId)) {
            SupplicantStaNetworkHal networkHandle = getCurrentNetworkRemoteHandle(ifaceName);
            if (networkHandle == null) {
                loge("Roaming config matches a linked config, but a linked network handle was"
                        + " not found.");
                return false;
            }
            return networkHandle.select();
        }
        if (getCurrentNetworkId(ifaceName) != config.networkId) {
            Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
                    + "Current network ID: " + getCurrentNetworkId(ifaceName));
            return connectToNetwork(ifaceName, config);
        }
        String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
        logd("roamToNetwork" + config.getProfileKey() + " (bssid " + bssid + ")");

        SupplicantStaNetworkHal networkHandle =
                checkSupplicantStaNetworkAndLogFailure(ifaceName, "roamToNetwork");
        if (networkHandle == null || !networkHandle.setBssid(bssid)) {
            loge("Failed to set new bssid on network: " + config.getProfileKey());
            return false;
        }
        if (!reassociate(ifaceName)) {
            loge("Failed to trigger reassociate");
            return false;
        }
        return true;
    }
}
  • 首先,检查是否正在尝试与已关联的网络进行连接(而不是漫游)。如果是,则选择已存在的Supplicant网络。
  • 如果当前网络与提供的网络配置不匹配,则触发新的连接尝试而不是漫游。
  • 如果匹配,设置新的BSSID并触发重新关联操作,以执行漫游。
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
public boolean reassociate(@NonNull String ifaceName) {
    synchronized (mLock) {
        final String methodStr = "reassociate";
        ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
        if (iface == null) return false;
        try {
            SupplicantStatus status = iface.reassociate();
            return checkStatusAndLogFailure(status, methodStr);
        } catch (RemoteException e) {
            handleRemoteException(e, methodStr);
            return false;
        }
    }
}

reassociate 方法中调用到了 wpa_supplicant 进程中。wpa_supplicant 于 kernel driver 层交互,实现 WiFi 重新关联。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值