1 WifiConnectivityManager

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

2 WifiManager.connect()

这个API没有发生变动.

public void connect(WifiConfiguration config, ActionListener listener) {
 if (config == nullthrow 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;
   }
......

看下WifiStateMachine,一般都是在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.
 */

2.1 看下①处的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中,

2.2 跑完①之后,跟着会跑②

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

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

3.1 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)
 
{
   ......
   }

3.2 connectToNetwork

对我们通过算法算出来的目标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. 在Visual Studio 2010/2012/2013/2015上使用C#开发Android/IOS安
  2. Android framework源码按键操作的完整流程
  3. android通用文件操作
  4. Android的用户
  5. Android 中的网络操作(HttpURLConnection)
  6. 浅入浅出 Android 安全:第三章 Android 本地用户空间层安全
  7. Android使用XML文件定义用户界面
  8. 【Android 开发】:数据存储之 SQLite 数据库操作(三)

随机推荐

  1. Android 编译环境配置搭建(Ubuntu 12.04)
  2. ShareSDK for Android(安卓)只有新浪微博
  3. 适用于Android的Google Fit:读取传感器数
  4. Android -Recovery
  5. Android SDK需要安装的包
  6. 【Flutter开发】------修改图标、应用名
  7. android笔试题
  8. Android核心模块内容概述
  9. Android ContentProvider的使用和理解
  10. Unity 导出 Gradle 工程及问题 (Please s