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);
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM