Android N wifi
Android N 的wifi架构真的是改动挺大,从文件目录看,添加了不少文件,实际上则是对整个wifi模块进行大卸耦,很多东西被才成独立的模块,便于维护,添加feature,这里貌似采用了门面设计模式,可以看FrameworkFacade.java,。下面就看下android N wifi scan都做了哪些修改。
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());
}
setScanResults
scanResults的上报,还是跟之前一样,接收supplicant的CMD。但是这个函数被重构了,写得更优雅了。用了一个类NetworkDetail来描述热点的信息。
总结
在scan模块,google主要添加了一个scanservice,独立出来一个service,添加了类似管理中心的东西,便于处理多种不同场景下的scan需求。在之前,wifi的scan确实引发了不少bug,希望重构之后,这个模块不要再打到其他模块了。