Android Wi-Fi wifi scan流程分析(Android 7.0)
2017-07-15 14:24
423 查看
1 Android N wifi
Android N 的wifi架构真的是改动挺大,从文件目录看,添加了不少文件,实际上则是对整个wifi模块进行大卸耦,很多东西被才成独立的模块,便于维护,添加feature,这里貌似采用了门面设计模式,可以看FrameworkFacade.Java,。下面就看下android N wifi scan都做了哪些修改。
2 startScan
startScan的API没有做什么变化,也是从wifiManager一直call到WIFiStateMachine里面。但是在WiFiStatemachine中处理CMD_START_SCAN这里就开始发生变化了。从handleScanRequest 开始看吧
/*WifiStateMachine.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi) */ private void handleScanRequest(Message message) { ScanSettings settings = null; WorkSource workSource = null; // unbundle parameters Bundle bundle = (Bundle) message.obj; if (bundle != null) { settings = bundle.getParcelable(CUSTOMIZED_SCAN_SETTING); workSource = bundle.getParcelable(CUSTOMIZED_SCAN_WORKSOURCE); } Set<Integer> freqs = null; //setting里面记录了fred的信息 if (settings != null && settings.channelSet != null) { freqs = new HashSet<Integer>(); for (WifiChannel channel : settings.channelSet) { freqs.add(channel.freqMHz); } } // Retrieve the list of hidden networkId's to scan for. Set<Integer> hiddenNetworkIds = mWifiConfigManager.getHiddenConfiguredNetworkIds(); // call wifi native to start the scan //这里是重点 if (startScanNative(freqs, hiddenNetworkIds, workSource)) { // a full scan covers everything, clearing scan request buffer if (freqs == null) mBufferedScanMsg.clear(); messageHandlingStatus = MESSAGE_HANDLING_STATUS_OK; if (workSource != null) { // External worksource was passed along the scan request, // hence always send a broadcast mSendScanResultsBroadcast = true; } return; } // if reach here, scan request is rejected ..... }
看下这个startScanNative都做了些什么,其实这个函数起了一个wifiScanner和WifiStatemachine之间的桥梁作用。
// TODO this is a temporary measure to bridge between WifiScanner and WifiStateMachine until // scan functionality is refactored out of WifiStateMachine. /** * return true iff scan request is accepted */ private boolean startScanNative(final Set<Integer> freqs, Set<Integer> hiddenNetworkIds, WorkSource workSource) { //创建了一个ScanSetting对象,目的是为了设置scan的频率模式 WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); if (freqs == null) { /*假如我们前面没有指定freqs是什么,这里默认会采用WIFI_BAND_BOTH_WITH_DFS Both 2.4 GHz band and 5 GHz band; with DFS channels public static final int WIFI_BAND_BOTH_WITH_DFS = 7; both bands with DFS channels */ settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; } else { settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; int index = 0; settings.channels = new WifiScanner.ChannelSpec[freqs.size()]; for (Integer freq : freqs) { settings.channels[index++] = new WifiScanner.ChannelSpec(freq); } } settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; if (hiddenNetworkIds != null && hiddenNetworkIds.size() > 0) { int i = 0; settings.hiddenNetworkIds = new int[hiddenNetworkIds.size()]; for (Integer netId : hiddenNetworkIds) { settings.hiddenNetworkIds[i++] = netId; } } //创建一个监听对象,这个后面调用mWifiScanner.startScan会用到 WifiScanner.ScanListener nativeScanListener = new WifiScanner.ScanListener() { // ignore all events since WifiStateMachine is registered for the supplicant events public void onSuccess() { } public void onFailure(int reason, String description) { mIsScanOngoing = false; mIsFullScanOngoing = false; } public void onResults(WifiScanner.ScanData[] results) { } public void onFullResult(ScanResult fullScanResult) { } public void onPeriodChanged(int periodInMs) { } }; //跳转到mWifiScanner.startScan() mWifiScanner.startScan(settings, nativeScanListener, workSource); mIsScanOngoing = true; mIsFullScanOngoing = (freqs == null); lastScanFreqs = freqs; return true; }
wifiScann是android N新添加进来的东西,其实这个一个client端。在android N,wifi scan被做成一个独立的service。这边是client端,通过asynchannel会发送CMD去service端,跑真正的scan函数。
//WifiScanner.java (frameworks\base\wifi\java\android\net\wifi) /** * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. */ public void startScan(ScanSettings settings, ScanListener listener) { startScan(settings, listener, null); } /** * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param workSource WorkSource to blame for power usage * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. */ public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { Preconditions.checkNotNull(listener, "listener cannot be null"); int key = addListener(listener); if (key == INVALID_KEY) return; validateChannel(); Bundle scanParams = new Bundle(); scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings); scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource); mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams); }
WifiScanningServiceImpl里面的状态机比较奇怪的,他维护了几类小状态机,针对不同的scan类型做不同的处理,我们这里是简单的从api 调用下来的SingleScan类型。其他的类型,后面再补上。
public void startService() { mClientHandler = new ClientHandler(mLooper); mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper); mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper); // mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper); mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra( WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); if (DBG) localLog("SCAN_AVAILABLE : " + state); if (state == WifiManager.WIFI_STATE_ENABLED) { mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED); mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED); mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED); } else if (state == WifiManager.WIFI_STATE_DISABLED) { mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED); } } }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); mBackgroundScanStateMachine.start(); mWifiChangeStateMachine.start(); mSingleScanStateMachine.start(); mPnoScanStateMachine.start(); }
mSingleScanStateMachine,里面有几个简单的状态。
/* WifiScanningServiceImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi\scanner) */ /** * State machine that holds the state of single scans. Scans should only be active in the * ScanningState. The pending scans and active scans maps are swaped when entering * ScanningState. Any requests queued while scanning will be placed in the pending queue and * executed after transitioning back to IdleState. */ WifiSingleScanStateMachine(Looper looper) { super("WifiSingleScanStateMachine", looper); setLogRecSize(128); setLogOnlyTransitions(false); // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mDriverStartedState, mDefaultState); addState(mIdleState, mDriverStartedState); addState(mScanningState, mDriverStartedState); // CHECKSTYLE:ON IndentationCheck setInitialState(mDefaultState); } class DefaultState extends State { @Override public void enter() { mActiveScans.clear(); mPendingScans.clear(); } @Override public boolean processMessage(Message msg) { switch (msg.what) { //前面如果收到发过来的driver已经加载的CMD_DRIVER_LOADED,则调到mIdleState case CMD_DRIVER_LOADED: transitionTo(mIdleState); .... } class IdleState extends State { @Override public void enter() { //这里会去做一次尝试扫描,即当driver加载成功的时候,会自己做一次scan的操作 tryToStartNewScan(); } @Override public boolean processMessage(Message msg) { return NOT_HANDLED; } }
所以我们当前其实是处于WifiSingleScanStateMachine.IdleState,当收到CMD_START_SINGLE_SCAN这个CMD的时候,IdleState自己根本无法处理,所以交个他的上级。即在DriverStartedState中处理
/** * State representing when the driver is running. This state is not meant to be transitioned * directly, but is instead indented as a parent state of ScanningState and IdleState * to hold common functionality and handle cleaning up scans when the driver is shut down. */ class DriverStartedState extends State { @Override public void exit() { mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED, mPendingScans.size()); sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted"); } @Override public boolean processMessage(Message msg) { ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case WifiScanner.CMD_START_SINGLE_SCAN: mWifiMetrics.incrementOneshotScanCount(); int handler = msg.arg2; Bundle scanParams = (Bundle) msg.obj; if (scanParams == null) { logCallback("singleScanInvalidRequest", ci, handler, "null params"); replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null"); return HANDLED; } scanParams.setDefusable(true); ScanSettings scanSettings = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); WorkSource workSource = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); if (validateAndAddToScanQueue(ci, handler, scanSettings, workSource)) { replySucceeded(msg); // If were not currently scanning then try to start a scan. Otherwise // this scan will be scheduled when transitioning back to IdleState // after finishing the current scan. //假如当前没有在scanning,那就try if (getCurrentState() != mScanningState) { tryToStartNewScan(); } } else { logCallback("singleScanInvalidRequest", ci, handler, "bad request"); replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request"); mWifiMetrics.incrementScanReturnEntry( WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1); } return HANDLED; .....
在tryToStartNewScan中做了一次scan之后,如果没有失败,则会跳转到mScanningState
void tryToStartNewScan() { if (mScannerImpl.startSingleScan(settings, this)) { // swap pending and active scan requests RequestList<ScanSettings> tmp = mActiveScans; mActiveScans = mPendingScans; mPendingScans = tmp; // make sure that the pending list is clear mPendingScans.clear(); transitionTo(mScanningState); }
mScannerImpl其实是抽象类WifiScannerImpl的对象,这只是一个接口,要找到实现方,才知道做了什么。google卸耦真是干净利落。
/* SupplicantWifiScannerImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi\scanner) */ public class SupplicantWifiScannerImpl extends WifiScannerImpl implements Handler.Callback { ......... @Override public boolean startSingleScan(WifiNative.ScanSettings settings, WifiNative.ScanEventHandler eventHandler) { if (eventHandler == null || settings == null) { Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings + ",eventHandler=" + eventHandler); return false; } if (mPendingSingleScanSettings != null || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) { Log.w(TAG, "A single scan is already running"); return false; } synchronized (mSettingsLock) { mPendingSingleScanSettings = settings; mPendingSingleScanEventHandler = eventHandler; //因为有多种scan共存,为防止打架,所以google搞了这个函数来做指挥 processPendingScans(); return true; } } ....... private void processPendingScans() { synchronized (mSettingsLock) { // Wait for the active scan result to come back to reschedule other scans, // unless if HW pno scan is running. Hw PNO scans are paused it if there // are other pending scans, if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) { return; } ChannelCollection allFreqs = mChannelHelper.createChannelCollection(); Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>(); final LastScanSettings newScanSettings = new LastScanSettings(mClock.elapsedRealtime()); // Update scan settings if there is a pending scan if (!mBackgroundScanPaused) { if (mPendingBackgroundScanSettings != null) { mBackgroundScanSettings = mPendingBackgroundScanSettings; mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler; mNextBackgroundScanPeriod = 0; mPendingBackgroundScanSettings = null; mPendingBackgroundScanEventHandler = null; mBackgroundScanPeriodPending = true; } if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) { int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets; ++bucket_id) { WifiNative.BucketSettings bucket = mBackgroundScanSettings.buckets[bucket_id]; if (mNextBackgroundScanPeriod % (bucket.period_ms / mBackgroundScanSettings.base_period_ms) == 0) { if ((bucket.report_events & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) { reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; } if ((bucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; } // only no batch if all buckets specify it if ((bucket.report_events & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) { reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH; } allFreqs.addChannels(bucket); } } if (!allFreqs.isEmpty()) { newScanSettings.setBackgroundScan(mNextBackgroundScanId++, mBackgroundScanSettings.max_ap_per_scan, reportEvents, mBackgroundScanSettings.report_threshold_num_scans, mBackgroundScanSettings.report_threshold_percent); } int[] hiddenNetworkIds = mBackgroundScanSettings.hiddenNetworkIds; if (hiddenNetworkIds != null) { int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN); for (int i = 0; i < numHiddenNetworkIds; i++) { hiddenNetworkIdSet.add(hiddenNetworkIds[i]); } } mNextBackgroundScanPeriod++; mBackgroundScanPeriodPending = false; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + mBackgroundScanSettings.base_period_ms, BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler); } } if (mPendingSingleScanSettings != null) { boolean reportFullResults = false; ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection(); for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) { WifiNative.BucketSettings bucketSettings = mPendingSingleScanSettings.buckets[i]; if ((bucketSettings.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) { reportFullResults = true; } singleScanFreqs.addChannels(bucketSettings); allFreqs.addChannels(bucketSettings); } newScanSettings.setSingleScan(reportFullResults, singleScanFreqs, mPendingSingleScanEventHandler); int[] hiddenNetworkIds = mPendingSingleScanSettings.hiddenNetworkIds; if (hiddenNetworkIds != null) { int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN); for (int i = 0; i < numHiddenNetworkIds; i++) { hiddenNetworkIdSet.add(hiddenNetworkIds[i]); } } mPendingSingleScanSettings = null; mPendingSingleScanEventHandler = null; } if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive) && !allFreqs.isEmpty()) { pauseHwPnoScan(); Set<Integer> freqs = allFreqs.getSupplicantScanFreqs(); boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet); if (success) { // TODO handle scan timeout if (DBG) { Log.d(TAG, "Starting wifi scan for freqs=" + freqs + ", background=" + newScanSettings.backgroundScanActive + ", single=" + newScanSettings.singleScanActive); } mLastScanSettings = newScanSettings; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + SCAN_TIMEOUT_MS, TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler); } else { Log.e(TAG, "Failed to start scan, freqs=" + freqs); // indicate scan failure async mEventHandler.post(new Runnable() { public void run() { if (newScanSettings.singleScanEventHandler != null) { newScanSettings.singleScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_FAILED); } } }); // TODO(b/27769665) background scans should be failed too if scans fail enough } } else if (isHwPnoScanRequired()) { newScanSettings.setHwPnoScan(mPnoEventHandler); if (startHwPnoScan()) { mLastScanSettings = newScanSettings; } else { Log.e(TAG, "Failed to start PNO scan"); // indicate scan failure async mEventHandler.post(new Runnable() { public void run() { if (mPnoEventHandler != null) { mPnoEventHandler.onPnoScanFailed(); } // Clean up PNO state, we don't want to continue PNO scanning. mPnoSettings = null; mPnoEventHandler = null; } }); } } } }
好吧上面的东西有点多,抽点重点出来看
if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive) && !allFreqs.isEmpty()) { pauseHwPnoScan(); Set<Integer> freqs = allFreqs.getSupplicantScanFreqs(); //下一个scan的命令下去 boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet); if (success) { // TODO handle scan timeout if (DBG) { Log.d(TAG, "Starting wifi scan for freqs=" + freqs + ", background=" + newScanSettings.backgroundScanActive + ", single=" + newScanSettings.singleScanActive); } mLastScanSettings = newScanSettings; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.elapsedRealtime() + SCAN_TIMEOUT_MS, TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler); } else { Log.e(TAG, "Failed to start scan, freqs=" + freqs); // indicate scan failure async mEventHandler.post(new Runnable() { public void run() { if (newScanSettings.singleScanEventHandler != null) { newScanSettings.singleScanEventHandler .onScanStatus(WifiNative.WIFI_SCAN_FAILED); } } }); // TODO(b/27769665) background scans should be failed too if scans fail enough }
最后跑到wifinative,这个和之前的差别不大,
/** * Start a scan using wpa_supplicant for the given frequencies. * @param freqs list of frequencies to scan for, if null scan all supported channels. * @param hiddenNetworkIds List of hidden networks to be scanned for. */ public boolean scan(Set<Integer> freqs, Set<Integer> hiddenNetworkIds) { String freqList = null; String hiddenNetworkIdList = null; if (freqs != null && freqs.size() != 0) { freqList = createCSVStringFromIntegerSet(freqs); } if (hiddenNetworkIds != null && hiddenNetworkIds.size() != 0) { hiddenNetworkIdList = createCSVStringFromIntegerSet(hiddenNetworkIds); } return scanWithParams(freqList, hiddenNetworkIdList); } private boolean scanWithParams(String freqList, String hiddenNetworkIdList) { StringBuilder scanCommand = new StringBuilder(); scanCommand.append("SCAN TYPE=ONLY"); if (freqList != null) { scanCommand.append(" freq=" + freqList); } if (hiddenNetworkIdList != null) { scanCommand.append(" scan_id=" + hiddenNetworkIdList); } return doBooleanCommand(scanCommand.toString()); }
3 setScanResults
scanResults的上报,还是跟之前一样,接收supplicant的CMD。但是这个函数被重构了,写得更优雅了。用了一个类NetworkDetail来描述热点的信息。
4 总结
在scan模块,google主要添加了一个scanservice,独立出来一个service,添加了类似管理中心的东西,便于处理多种不同场景下的scan需求。在之前,wifi的scan确实引发了不少bug,希望重构之后,这个模块不要再打到其他模块了。
相关文章推荐
- Android Wi-Fi connect & auto connect流程分析(Android 7.0)
- Android N Wi-Fi 扫描流程分析
- Android N Wi-Fi 启动流程分析
- Android 7.0 ActivityManagerService(6) Service相关流程分析
- [Network]Android N 新wifi scan流程分析
- Android 7.0应用冷启动流程分析
- Android 7.0系统启动流程分析
- android 7.0 关机流程详细分析
- Android Wi-Fi 设置带宽代码流程
- Android 7.0 虚拟按键(NavigationBar)源码分析(一) 之 View的创建流程
- Android 7.0 Keyguard流程分析
- Android 7.0 虚拟按键(NavigationBar)源码分析 之 View的创建流程
- Android 7.0 ActivityManagerService(7) 进程管理相关流程分析(1)
- Android 7.0 ActivityManagerService 广播(Broadcast)相关流程分析
- Android 系统7.0上 屏幕背光流程分析
- Android 7.0 ActivityManagerService(5) 广播(Broadcast)相关流程分析
- Android Wi-Fi源码分析之WifiService操作Wi-Fi(二):WifiStateMachine.java中的SUP_CONNECTION_EVENT分析
- Android 7.0 虚拟按键(NavigationBar)源码分析 之 点击事件的实现流程
- Android 7.0 ActivityManagerService(9) 进程管理相关流程分析(3) computeOomAdjLocked
- Android 7.0系统启动流程分析