Android N wifi auto connect流程分析

定义

       当有两个或者两个以上的已经保存的无线网络可以连接时,系统通过选择算法来选择一个最优网络。

  • 在Android L,wifi的自动重连机制是由WifiAutoJoinController 类来实现,核心的方法就是attemptAutoJoin(),
  • 然而,android L这个机制和用户connect的flow会产生冲突,出现了很多的bug,很鸡肋。
  • 因此,android N对这个auto connect的部分做了大改

实现

       auto connect在许多场景都会用到,如开机自动连接、亮屏扫描连接等等,这里我们看亮屏扫描时如何自动重连以及选择最优网络的
       startPeriodicScan,这个是当屏幕是亮屏的时候,后台一直做scan的操作。
 
/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java

// Start a periodic scan when screen is on
private void startPeriodicScan(boolean scanImmediately) {
    mPnoScanListener.resetLowRssiNetworkRetryDelay();

    // No connectivity scan if auto roaming is disabled.
    if (mWifiState == WIFI_STATE_CONNECTED
            && !mConfigManager.getEnableAutoJoinWhenAssociated()) {
        return;
    }

    // Due to b/28020168, timer based single scan will be scheduled
    // to provide periodic scan in an exponential backoff fashion.
    if (!ENABLE_BACKGROUND_SCAN) {
        if (scanImmediately) {
            resetLastPeriodicSingleScanTimeStamp();
        }
        mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
        startPeriodicSingleScan();
    } else {
        ScanSettings settings = new ScanSettings();
        settings.band = getScanBand();
        settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
                            | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
        settings.numBssidsPerScan = 0;
        settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS;

        mPeriodicScanListener.clearScanDetails();
        mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE);
    }
}

看下这里传入的监听器:mPeriodicScanListener

/*
WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi) 
*/

    // Periodic scan results listener. A periodic scan is initiated when
    // screen is on.
    private class PeriodicScanListener implements WifiScanner.ScanListener {
        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();

        public void clearScanDetails() {
            mScanDetails.clear();
        }

        @Override
        public void onSuccess() {
            localLog("PeriodicScanListener onSuccess");

            // reset the count
            mScanRestartCount = 0;
        }

        @Override
        public void onFailure(int reason, String description) {
            Log.e(TAG, "PeriodicScanListener onFailure:"
                          + " reason: " + reason
                          + " description: " + description);

            // reschedule the scan
            if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
                scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
            } else {
                mScanRestartCount = 0;
                Log.e(TAG, "Failed to successfully start periodic scan for "
                          + MAX_SCAN_RESTART_ALLOWED + " times");
            }
        }

        @Override
        public void onPeriodChanged(int periodInMs) {
            localLog("PeriodicScanListener onPeriodChanged: "
                          + "actual scan period " + periodInMs + "ms");
        }

        @Override
        public void onResults(WifiScanner.ScanData[] results) {
            //当scan到了结果之后,跑handleScanResults去处理
            handleScanResults(mScanDetails, "PeriodicScanListener");
            clearScanDetails();
        }

        @Override
        public void onFullResult(ScanResult fullScanResult) {
            if (mDbg) {
                localLog("PeriodicScanListener onFullResult: "
                            + fullScanResult.SSID + " capabilities "
                            + fullScanResult.capabilities);
            }

            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
        }
    }

handleScanResults会去调用QualifiedNetworkSelector.selectQualifiedNetwork去筛选目标ssid,比如:

  • RSSI
  • 5G or 2.4G
  • lastUserSelectedNetworkId
  • 等等

我写了一篇关于selectQualifiedNetwork的详细分析,大家需要详细了解评分质量算法的,可以看Android N selectQualifiedNetwork分析


    /**
     * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
     * Executes selection of potential network candidates, initiation of connection attempt to that
     * network.
     *
     * @return true - if a candidate is selected by QNS
     *         false - if no candidate is selected by QNS
     */
    private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) {
        localLog(listenerName + " onResults: start QNS");
        //调用QualifiedNetworkSelector去筛选目标ssid,这个算法有点复杂。我看下参数就好
        ①WifiConfiguration candidate =
                mQualifiedNetworkSelector.selectQualifiedNetwork(false,
                mUntrustedConnectionAllowed, scanDetails,
                mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
                mStateMachine.isDisconnected(),
                mStateMachine.isSupplicantTransientState());
        mWifiLastResortWatchdog.updateAvailableNetworks(
                mQualifiedNetworkSelector.getFilteredScanDetails());
        if (candidate != null) {
            localLog(listenerName + ": QNS candidate-" + candidate.SSID);
            ②connectToNetwork(candidate);
            return true;
        } else {
            return false;
        }
    }

selectQualifiedNetwork:

当它获得新的扫描结果时,应该在连通性管理器中调用它
检查是否需要进行网络选择。如果需要,检查所有新的扫描结果和
选择一个新的符合条件的网络/BSSID来连接

    /**
     * ToDo: This should be called in Connectivity Manager when it gets new scan result
     * check whether a network slection is needed. If need, check all the new scan results and
     * select a new qualified network/BSSID to connect to
     *
     * @param forceSelectNetwork true -- start a qualified network selection anyway,no matter
     *                           current network is already qualified or not.
     *                           false -- if current network is already qualified, do not do new
     *                           selection
     * @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network
     *                                      false -- user do not allow to connect to untrusted
     *                                      network
     * @param scanDetails latest scan result obtained (should be connectivity scan only)
     * @param isLinkDebouncing true -- Link layer is under debouncing
     *                         false -- Link layer is not under debouncing
     * @param isConnected true -- device is connected to an AP currently
     *                    false -- device is not connected to an AP currently
     * @param isDisconnected true -- WifiStateMachine is at disconnected state
     *                       false -- WifiStateMachine is not at disconnected state
     * @param isSupplicantTransient true -- supplicant is in a transient state
     *                              false -- supplicant is not in a transient state
     * @return the qualified network candidate found. If no available candidate, return null
     */
    public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork ,
            boolean isUntrustedConnectionsAllowed, List<ScanDetail>  scanDetails,
            boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
            boolean isSupplicantTransient) {

connectToNetwork(candidate);对我们通过算法算出来的目标ssid发起连接,candidate是一个WifiConfiguration对象。

    /**
     * Attempt to connect to a network candidate.
     *
     * Based on the currently connected network, this menthod determines whether we should
     * connect or roam to the network candidate recommended by QNS.
     */
    private void connectToNetwork(WifiConfiguration candidate) {
        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
        if (scanResultCandidate == null) {
            Log.e(TAG, "connectToNetwork: bad candidate - "  + candidate
                    + " scanResult: " + scanResultCandidate);
            return;
        }

        String targetBssid = scanResultCandidate.BSSID;
        String targetAssociationId = candidate.SSID + " : " + targetBssid;

        //如果我们筛选出来的ssid正好是上一次attempt连接的ssid,或者是supplicant现在正在连接的目标ssid,则放弃
        // Check if we are already connected or in the process of connecting to the target
        // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
        // in case the firmware automatically roamed to a BSSID different from what QNS
        // selected.
        if (targetBssid != null
                && (targetBssid.equals(mLastConnectionAttemptBssid)
                    || targetBssid.equals(mWifiInfo.getBSSID()))
                && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
            localLog("connectToNetwork: Either already connected "
                    + "or is connecting to " + targetAssociationId);
            return;
        }

        Long elapsedTimeMillis = mClock.elapsedRealtime();  
            /**
     * This checks the connection attempt rate and recommends whether the connection attempt
     * should be skipped or not. This attempts to rate limit the rate of connections to
     * prevent us from flapping between networks and draining battery rapidly.
     */    
         //控制attempt连接速率,如果间隔时间太快,那就放弃  
        if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
            localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
            mTotalConnectivityAttemptsRateLimited++;
            return;
        }
        //终于要开始连接了,把时间记录下来noteConnectionAttempt
        noteConnectionAttempt(elapsedTimeMillis);

        mLastConnectionAttemptBssid = targetBssid;

        WifiConfiguration currentConnectedNetwork = mConfigManager
                .getWifiConfiguration(mWifiInfo.getNetworkId());
        String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
                (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());

        //做漫游操作还是autoconnect的操作
        if (currentConnectedNetwork != null
                && (currentConnectedNetwork.networkId == candidate.networkId
                || currentConnectedNetwork.isLinked(candidate))) {
            localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
                        + targetAssociationId);
            mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
        } else {
            localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
                        + targetAssociationId);
            mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
        }
    }

接下来就交给Wifistatemachine去处理了。


    /**
     * Automatically connect to the network specified
     *
     * @param networkId ID of the network to connect to
     * @param bssid BSSID of the network
     */
    public void autoConnectToNetwork(int networkId, String bssid) {
        sendMessage(CMD_AUTO_CONNECT, networkId, 0, bssid);
    }

    /**
     * Automatically roam to the network specified
     *
     * @param networkId ID of the network to roam to
     * @param scanResult scan result which identifies the network to roam to
     */
    public void autoRoamToNetwork(int networkId, ScanResult scanResult) {
        sendMessage(CMD_AUTO_ROAM, networkId, 0, scanResult);
    }
posted @ 2021-01-28 11:19  肉滚滚和代码  阅读(1410)  评论(0编辑  收藏  举报