WifiConnectivityManager

前面提到的新 Android N scan机制 ,现在再看一个新的东西,WifiConnectivityManager,之前android connect一个wifi和做scan的操作都是放在wifistatemachine中的,整个看起来很杂乱。现在google在android N中做了个新的东西,WifiConnectivityManager,通过这个东西来管理scan和connect。这里我从wifi connect一个ssid的流程来看这个WifiConnectivityManager.

WifiManager.connect()

这个API没有发生变动。

    public void connect(WifiConfiguration config, ActionListener listener) {        if (config == null) throw new IllegalArgumentException("config cannot be null");        // Use INVALID_NETWORK_ID for arg1 when passing a config object        // arg1 is used to pass network id when the network already exists        getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,                putListener(listener), config);    }

在WifiserviceImpl.java中会收到这个CMD,跟着做相应的处理

/*WifiServiceImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)*/     */    private class ClientHandler extends Handler {        ClientHandler(Looper looper) {            super(looper);        }         @Override        public void handleMessage(Message msg) {         ............                      case WifiManager.CONNECT_NETWORK:                case WifiManager.SAVE_NETWORK: {                    WifiConfiguration config = (WifiConfiguration) msg.obj;                    int networkId = msg.arg1;                    if (msg.what == WifiManager.SAVE_NETWORK) {                        Slog.d("WiFiServiceImpl ", "SAVE"                                + " nid=" + Integer.toString(networkId)                                + " uid=" + msg.sendingUid                                + " name="                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));                    }                    if (msg.what == WifiManager.CONNECT_NETWORK) {                        Slog.d("WiFiServiceImpl ", "CONNECT "                                + " nid=" + Integer.toString(networkId)                                + " uid=" + msg.sendingUid                                + " name="                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));                    }                    if (config != null && isValid(config)) {                        if (DBG) Slog.d(TAG, "Connect with config" + config);                        //直接给WifiStatemachine把这个CMD送过去                        mWifiStateMachine.sendMessage(Message.obtain(msg));                    } else if (config == null                            && networkId != WifiConfiguration.INVALID_NETWORK_ID) {                        if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);                        mWifiStateMachine.sendMessage(Message.obtain(msg));                    } else {                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);                        if (msg.what == WifiManager.CONNECT_NETWORK) {                            replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,                                    WifiManager.INVALID_ARGS);                        } else {                            replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,                                    WifiManager.INVALID_ARGS);                        }                    }                    break;                }.........................

看下wifistatemachinemachine,一般都是在ConnectModeState中会处理这个:

/*WifiStateMachine.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)    */class ConnectModeState extends State {        ..........       case WifiManager.CONNECT_NETWORK:       .............................                    /* Tell network selection the user did try to connect to that network if from                    settings */                    boolean persist =                        mWifiConfigManager.checkConfigOverridePermission(message.sendingUid);//设置mLastSelectedConfiguration变量,表示最近一次用户选择连接的热点                    mWifiConfigManager.setAndEnableLastSelectedConfiguration(netId);                    if (mWifiConnectivityManager != null) {                        //通过WifiConnectivityManager去连接                        mWifiConnectivityManager.connectToUserSelectNetwork(netId, persist);                    }                //Start a new ConnectionEvent due to connect_network, this is always user                    //selected                    mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,                            WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);                    if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,                            message.sendingUid) && mWifiNative.reconnect()) {                        lastConnectAttemptTimestamp = System.currentTimeMillis();                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);                        /* The state tracker handles enabling networks upon completion/failure */                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);                        if (didDisconnect) {                            /* Expect a disconnection from the old connection */                            transitionTo(mDisconnectingState);                        } else if (updatedExisting && getCurrentState() == mConnectedState &&                                getCurrentWifiConfiguration().networkId == netId) {                            // Update the current set of network capabilities, but stay in the                            // current state.                            updateCapabilities(config);                        } else {                            /**                             * Directly go to disconnected state where we                             * process the connection events from supplicant                             */                            transitionTo(mDisconnectedState);                        }                    } else {                        loge("Failed to connect config: " + config + " netId: " + netId);                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,                                WifiManager.ERROR);                        reportConnectionAttemptEnd(                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,                                WifiMetricsProto.ConnectionEvent.HLF_NONE);                        break;                    }

连接一个用户指定的SSID

/*WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi) */    /**     * Handler when user specifies a particular network to connect to     */    public void connectToUserSelectNetwork(int netId, boolean persistent) {        Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId                   + " persist=" + persistent);        //QualifiedNetworkSelector ssid质量选择器       ① mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);        clearConnectionAttemptTimeStamps();    }

WifiQualifiedNetworkSelector

其实这个class的设计是用于自动连接一个信号比较好的wifi ssid的。把用户连接一个热点的逻辑整合在这里,应该是为了避免在android 5.1经常出现的用户触发一个连接热点的操作,结果后台auto connect自己选择一个热点也去连接了,结果两个flow打架

/** * This class looks at all the connectivity scan results then * select an network for the phone to connect/roam to. */

看下①处的userSelectNetwork

    /**     * This API is called when user explicitly select a network. Currently, it is used in following     * cases:     * (1) User explicitly choose to connect to a saved network     * (2) User save a network after add a new network     * (3) User save a network after modify a saved network     * Following actions will be triggered:     * 1. if this network is disabled, we need re-enable it again     * 2. we considered user prefer this network over all the networks visible in latest network     *    selection procedure     *     * @param netId new network ID for either the network the user choose or add     * @param persist whether user has the authority to overwrite current connect choice     * @return true -- There is change made to connection choice of any saved network     *         false -- There is no change made to connection choice of any saved network     */    public boolean userSelectNetwork(int netId, boolean persist) {        WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);        localLog("userSelectNetwork:" + netId + " persist:" + persist);        if (selected == null || selected.SSID == null) {            localLoge("userSelectNetwork: Bad configuration with nid=" + netId);            return false;        }        //看下用户选择的这个wifi是不是enabled        if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {            mWifiConfigManager.updateNetworkSelectionStatus(netId,                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);        }        if (!persist) {            localLog("User has no privilege to overwrite the current priority");            return false;        }        boolean change = false;        String key = selected.configKey();        // This is only used for setting the connect choice timestamp for debugging purposes.        long currentTime = mClock.currentTimeMillis();        //遍历一下sacn result中所有的ssid,看下哪一个是用户selected的        List savedNetworks = mWifiConfigManager.getSavedNetworks();        for (WifiConfiguration network : savedNetworks) {            WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();            if (config.networkId == selected.networkId) {                if (status.getConnectChoice() != null) {                    //把旧的记录着用户selected的ssid remove掉                    localLog("Remove user selection preference of " + status.getConnectChoice()                            + " Set Time: " + status.getConnectChoiceTimestamp() + " from "                            + config.SSID + " : " + config.networkId);                    status.setConnectChoice(null);                    status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus                            .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);                    change = true;                }                continue;            }            //把当前的这个添加进去            if (status.getSeenInLastQualifiedNetworkSelection()                    && (status.getConnectChoice() == null                    || !status.getConnectChoice().equals(key))) {                localLog("Add key:" + key + " Set Time: " + currentTime + " to "                        + getNetworkString(config));                status.setConnectChoice(key);                status.setConnectChoiceTimestamp(currentTime);                change = true;            }        }        //Write this change to file        //写到wificonfig中        if (change) {            mWifiConfigManager.writeKnownNetworkHistory();            return true;        }        return false;    }

到这里的就结束了。。。。。这部分逻辑应该是为防止attempt auto join去跑的逻辑。

回到前面的WiFiConnectivityManager中,跑完①之后,跟着会跑②

    public void connectToUserSelectNetwork(int netId, boolean persistent) {        Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId                   + " persist=" + persistent);        ①mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);        ②clearConnectionAttemptTimeStamps();    }

②的代码很简单,调用了ConnectionAttemptTimeStamps,这个是管理wifi auto connect存在每次尝试去连接的时间戳,ConnectionAttemptTimeStamps是一个双向列表。

    /**     * This is used to clear the connection attempt rate limiter. This is done when the user     * explicitly tries to connect to a specified network.     */    private void clearConnectionAttemptTimeStamps() {        mConnectionAttemptTimeStamps.clear();    }

回到前面的WifiStatemachine中,跟着后面直接call mWifiNative.reconnect()去连接一个SSID。

                    if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,                            message.sendingUid) && mWifiNative.reconnect()) {                        lastConnectAttemptTimestamp = System.currentTimeMillis();                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);                        /* The state tracker handles enabling networks upon completion/failure */                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);                        if (didDisconnect) {                            /* Expect a disconnection from the old connection */                            transitionTo(mDisconnectingState);                        } 

Auto connect

android N对这个auto connect的部分做了大改,重新做了。这个Auto connect的机制其实是为了做wifi 热点的漫游用,即当找到信号等质量更好的热点的时候,就会切过去。但是在之前,特别是android L这个部分和用户connect的flow产生冲突,出现了很多的bug,很鸡肋。

前面
前面提到的新 Android N scan机制 提到了几种scan场景的应用,这个auto connect都会用到。这里我们就只分析一种case就好。

startPeriodicScan,这个是当屏幕是亮屏的时候,后台一直做scan的操作。

/*WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi) */    // Start a periodic scan when screen is on    private void startPeriodicScan(boolean scanImmediately) {        mPnoScanListener.resetLowRssiNetworkRetryDelay();        // 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去跑scan,worksource 是WIFI_WORK_SOURCE            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 mScanDetails = new ArrayList();        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));        }    }

看下这个函数都做了些什么?

    /**     * 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 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

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

更多相关文章

  1. Android系统开发
  2. Android用户界面——通用布局对象
  3. Android(安卓)SystemUI 的一些主要操作
  4. android发送http请求—-URLConnection、HttpURLConnection的使用
  5. Android(安卓)IPC进程间通讯机制学习笔记
  6. Android(安卓)Arduino 蓝牙模块通信源代码
  7. 网络连接之——xUtils 介绍(三)
  8. android:allowTaskReparenting(clearTaskOnLaunch...)
  9. 杂乱之android的Spinner应用

随机推荐

  1. 0319作业
  2. SMTP 协议入门教程
  3. 一些Java开发人员在编程中常见的雷!
  4. CentOS7.6 安装 QtSDK 出错:You need a C+
  5. CSS中使用grid进行布局的应用
  6. 图片
  7. Zookeeper一致性协议Zab详解
  8. 0323作业
  9. React17 系统精讲 结合TS打造旅游电商平
  10. HTML5画布如何设置线的样式?