[Network]Android(安卓)N 新wifi connect&auto connect流程分析
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); }
更多相关文章
- Android系统开发
- Android用户界面——通用布局对象
- Android(安卓)SystemUI 的一些主要操作
- android发送http请求—-URLConnection、HttpURLConnection的使用
- Android(安卓)IPC进程间通讯机制学习笔记
- Android(安卓)Arduino 蓝牙模块通信源代码
- 网络连接之——xUtils 介绍(三)
- android:allowTaskReparenting(clearTaskOnLaunch...)
- 杂乱之android的Spinner应用