[Android]Android P(9) WIFI学习笔记 - 扫描 (1)
目录
- 前言
- 请求扫描
- APP入口
- WifiManager
- system_server层
- WifiServiceImpl
- ScanRequestProxy
- WifiScanner
- WifiScanningServiceImpl
- WificondScannerImpl
- WifiNative
- WificondControl
- 总结
前言
- 基于Android P源码学习;
- 代码片为了方便阅读段经过删、裁减,请以实际源码为准;
请求WLAN芯片开始扫描,然后获取扫描结果,整个过程是分两个阶段:
- 请求扫描
- 获取结果
本篇会介绍请求扫描阶段的流程,而后者会在下一篇中梳理;
请求扫描
APP入口
WifiManager
//frameworks/base/wifi/java/android/net/wifi/WifiManager.java
/**
* Request a scan for access points. Returns immediately. The availability
* of the results is made known later by means of an asynchronous event sent
* on completion of the scan.
* <p>
* To initiate a Wi-Fi scan, declare the
* {@link android.Manifest.permission#CHANGE_WIFI_STATE}
* permission in the manifest, and perform these steps:
* </p>
* <ol style="1">
* <li>Invoke the following method:
* {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
* <li>
* Register a BroadcastReceiver to listen to
* {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
* <li>When a broadcast is received, call:
* {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
* </ol>
* @return {@code true} if the operation succeeded, i.e., the scan was initiated.
* @deprecated The ability for apps to trigger scan requests will be removed in a future
* release.
*/
@Deprecated
public boolean startScan() {
return startScan(null);
}
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
public boolean startScan(WorkSource workSource) {
try {
String packageName = mContext.getOpPackageName();
return mService.startScan(packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
注意:
- 返回结果仅仅代表是否已经成功下发“开始扫描”的请求,并不代表扫描过程是否成功;
- 扫描结果就位后,系统通过
SCAN_RESULTS_AVAILABLE_ACTION
广播通知; - 该接口已经标记为
deprecated
,且表示,后续Android版本不再允许APP侧发起扫描请求(这个后面会尝试解释其原因)
然后,我们来到IWifiManager.startScan()
的接口实现中(略去不重要的逻辑):
system_server层
WifiServiceImpl
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
public boolean startScan(String packageName) {
...
long ident = Binder.clearCallingIdentity();
...
try {
//权限检查
mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, callingUid);
//用于在lambda代码块中更新字段(基本数据类型若需要在lambda代码块中使用,必须声明为final,这无法满足重新赋值的需求)
Mutable<Boolean> scanSuccess = new Mutable<>();
//切换到WifiStateMachine执行,但在当前线程等待其完成,超时设定默认4s
boolean runWithScissorsSuccess = mWifiInjector.getWifiStateMachineHandler()
.runWithScissors(() -> {
//关键逻辑
scanSuccess.value = mScanRequestProxy.startScan(callingUid, packageName);
}, RUN_WITH_SCISSORS_TIMEOUT_MILLIS);
if (!runWithScissorsSuccess) {
Log.e(TAG, "Failed to post runnable to start scan");
//发送广播告知该次请求失败(通常为超时)
sendFailedScanBroadcast();
return false;
}
//如果runWithScissorsSuccess,则表示scanSuccess.value不可能为null,因此可以直接拆箱判断
if (!scanSuccess.value) {
Log.e(TAG, "Failed to start scan");
return false;
}
} catch (SecurityException e) {
return false;
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
小结:
- 请求开始扫描的逻辑在
ScanRequestProxy.startScan()
中; - 请求开始扫描的逻辑运行在
WifiStateMachine
线程,但当前调用线程会等待其执行完成; - 在获取到返回结果,或超时后,调用线程会基于此,决定
startScan
方法的最终返回值;
ScanRequestProxy
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/ScanRequestProxy.java
/**
* Initiate a wifi scan.
*
* @param callingUid The uid initiating the wifi scan. Blame will be given to this uid.
* @return true if the scan request was placed or a scan is already ongoing, false otherwise.
*/
public boolean startScan(int callingUid, String packageName) {
//获取WifiScanner的实例对象
if (!retrieveWifiScannerIfNecessary()) {
Log.e(TAG, "Failed to retrieve wifiscanner");
sendScanResultFailureBroadcastToPackage(packageName);
return false;
}
//判断调用方是否具备android.Manifest.permission.NETWORK_SETTINGS或android.Manifest.permission.NETWORK_SETUP_WIZARD权限
boolean fromSettingsOrSetupWizard =
mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid)
|| mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid);
// 如果不具备上述两个权限,限制其在短时间内频繁请求扫描 if (!fromSettingsOrSetupWizard
&& shouldScanRequestBeThrottledForApp(callingUid, packageName)) {
Log.i(TAG, "Scan request from " + packageName + " throttled");
sendScanResultFailureBroadcastToPackage(packageName);
return false;
}
// 如果允许请求扫描,则继续
WorkSource workSource = new WorkSource(callingUid);
WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
// 具备上面两条权限的进程,使用高精度扫描
if (fromSettingsOrSetupWizard) {
settings.type = WifiScanner.TYPE_HIGH_ACCURACY;
}
// 扫描包含所有支持的频段
settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
| WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
// 如果需要扫描隐藏网络,则将其添加到hiddenNetworks成员变量中;
if (mScanningForHiddenNetworksEnabled) {
// retrieve the list of hidden network SSIDs to scan for, if enabled.
List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList =
mWifiConfigManager.retrieveHiddenNetworkList();
settings.hiddenNetworks = hiddenNetworkList.toArray(
new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
}
//发起扫描请求
mWifiScanner.startScan(settings, new ScanRequestProxyScanListener(), workSource);
//标记mIsScanProcessingComplete为false,表示有一个扫描请求正在进行;
mIsScanProcessingComplete = false;
return true;
}
小结:
- 该方法主要进行了一些权限检查,并对不同权限、不同运行情况(前后台)的进程发起的请求进行对应的限制、参数调整等;
- 完成上述判断后,调用
WifiScanner.startScan
发起扫描请求; ScanRequestProxyScanListener
用于监听扫描实际返回状态与结果;mIsScanProcessingComplete
的作用是,对于连续两次扫描请求的结果,只广播第一次的结果;(是否合理存疑)
那么接下来,我们来到WifiScanner.startScan
:
WifiScanner
//frameworks/base/wifi/java/android/net/wifi/WifiScanner.java
/**
* 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.
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
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.
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
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);
}
小结:
- 使用
AsyncChannel
通知服务端响应消息CMD_START_SINGLE_SCAN
; - 两个key传递了之前在
ScanRequestProxy.startScan()
中封装的两个参数; AsyncChannel
的实现这里就不展开了,具体实现的讲解已经在准备中了,结论就是,这里的CMD_START_SINGLE_SCAN
,会在WifiScanningServiceImpl.ClientHandler.handleMessage()
中被处理;
WifiScanningServiceImpl
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
private class ClientHandler extends WifiHandler {
...
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
...
switch (msg.what) {
...
case WifiScanner.CMD_START_SINGLE_SCAN:
case WifiScanner.CMD_STOP_SINGLE_SCAN:
mSingleScanStateMachine.sendMessage(Message.obtain(msg));
break;
...
}
}
略去了大量不相关的代码,结果发现实际上是通过WifiSingleScanStateMachine.sendMessage
将其转发到了其他线程处理(WifiScanningService
线程);
这里需要将WifiSingleScanStateMachine
状态机的工作情况介绍一下了:
- 初始状态为
DefaultState
; - 在接收到广播
WifiManager.WIFI_SCAN_AVAILABLE
时,如果Intent
中key为WifiManager.EXTRA_SCAN_AVAILABLE
的值等于WifiManager.WIFI_STATE_ENABLED
,则发送消息CMD_DRIVER_LOADED
; - 在接收到广播
WifiManager.WIFI_SCAN_AVAILABLE
时,如果Intent
中key为WifiManager.EXTRA_SCAN_AVAILABLE
的值等于WifiManager.WIFI_STATE_DISABLED
,则发送消息CMD_DRIVER_UNLOADED
;(此处不涉及,就不过多展开) CMD_DRIVER_LOADED
在DefaultState
中收到,会将状态机拨到IdleState
;- 而只要不关闭WLAN,后续主要就只在
IdleState
与ScanningState
两者之间切换; - 顾名思义,触发扫描,就会由
IdleState
拨到ScanningState
,扫描结束后复位到IdleState
;
回到上面的内容,WifiScanningServiceImpl.ClientHandler
在发送的CMD_START_SINGLE_SCAN
,在如下几个状态内会被处理:
DefaultState
class DefaultState extends State { ... @Override public boolean processMessage(Message msg) { switch (msg.what) { ... case WifiScanner.CMD_START_SINGLE_SCAN: case WifiScanner.CMD_STOP_SINGLE_SCAN: replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available"); return HANDLED; ... default: return NOT_HANDLED; } } ... }
DriverStartedState
class DriverStartedState extends State { ... @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; ... scanParams.setDefusable(true); ScanSettings scanSettings = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY); WorkSource workSource = scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY); if (validateScanRequest(ci, handler, scanSettings)) { logScanRequest("addSingleScanRequest", ci, handler, workSource, scanSettings, null); replySucceeded(msg); // 如果当前正在进行扫描,则: // 1. 当前扫描可以满足这次请求(参数匹配),则会在此次扫描结果返回后,直接将其返回给请求方; // 2. 当前扫描不能满足这次请求,则会将其添加到等待队列中,待状态机拨回IdleState后,再触发扫描; // 如果当前没有进行扫描,则调用tryToStartNewScan请求扫描; if (getCurrentState() == mScanningState) { if (activeScanSatisfies(scanSettings)) { mActiveScans.addRequest(ci, handler, workSource, scanSettings); } else { mPendingScans.addRequest(ci, handler, workSource, scanSettings); } } else { mPendingScans.addRequest(ci, handler, workSource, scanSettings); tryToStartNewScan(); } } else { ... } return HANDLED; case WifiScanner.CMD_STOP_SINGLE_SCAN: removeSingleScanRequest(ci, msg.arg2); return HANDLED; default: return NOT_HANDLED; } } ... }
不难看出,上面逻辑最终有效的调用是tryToStartNewScan()
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
void tryToStartNewScan() {
if (mPendingScans.size() == 0) { // no pending requests
return;
}
mChannelHelper.updateChannels();
// TODO move merging logic to a scheduler
WifiNative.ScanSettings settings = new WifiNative.ScanSettings();
settings.num_buckets = 1;
WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
bucketSettings.bucket = 0;
bucketSettings.period_ms = 0;
bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
ChannelCollection channels = mChannelHelper.createChannelCollection();
List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
//将mPendingScans中所有保存的请求做参数合并(取并集)
for (RequestInfo<ScanSettings> entry : mPendingScans) {
settings.scanType =
mergeScanTypes(settings.scanType, getNativeScanType(entry.settings.type));
channels.addChannels(entry.settings);
if (entry.settings.hiddenNetworks != null) {
for (int i = 0; i < entry.settings.hiddenNetworks.length; i++) {
WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork();
hiddenNetwork.ssid = entry.settings.hiddenNetworks[i].ssid;
hiddenNetworkList.add(hiddenNetwork);
}
}
if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
!= 0) {
bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
}
}
if (hiddenNetworkList.size() > 0) {
settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()];
int numHiddenNetworks = 0;
for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) {
settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork;
}
}
channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
//正式发送请求
if (mScannerImpl.startSingleScan(settings, this)) {
mActiveScanSettings = settings;
// 交换mActiveScans与mPendingScans变量的引用,并在交换后将mPendingScans清空
RequestList<ScanSettings> tmp = mActiveScans;
mActiveScans = mPendingScans;
mPendingScans = tmp;
mPendingScans.clear();
// 状态拨至ScanningState
transitionTo(mScanningState);
} else {
...
}
}
以上逻辑梳理如下:
- 无论是
IdleState
还是ScanningState
,都没有在自己子状态内处理WifiScanner.CMD_START_SINGLE_SCAN
,而是在父状态DriverStartedState
中处理的; - 如果处理时状态机置于
IdleState
状态,则将扫描请求封装后添加到mPendingScans
中,并调用tryToStartNewScan()
触发扫描; tryToStartNewScan()
主要调用- 如果处理时状态机置于
ScanningState
状态,需要视情况而定:- 如果当前扫描请求
mActiveScans
的扫描参数满足这次请求,则不再另行请求; - 如果不能满足,则将此次扫描请求的参数添加到
mPendingScans
中;
- 如果当前扫描请求
- 当扫描结束后,
ScanningState
会清空mActiveScans
,并将状态拨回IdleState
; - 而
IdleState
在enter()
方法处,会再次调用tryToStartNewScan
,来将mPendingScans
中的请求通知到WifiScannerImpl
;
最后,我们终于来到了WifiScannerImpl.startSingleScan()
这个抽象方法的实现:
WifiScannerImpl
有两个子类:
HalWifiScannerImpl
WificondScannerImpl
借由一个工厂设计模式,由``的结果来决定构造哪个实现类:
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
/**
* Factory that create the implementation that is most appropriate for the system.
* This factory should only ever be used once.
*/
public static final WifiScannerImplFactory DEFAULT_FACTORY = new WifiScannerImplFactory() {
public WifiScannerImpl create(Context context, Looper looper, Clock clock) {
WifiNative wifiNative = WifiInjector.getInstance().getWifiNative();
WifiMonitor wifiMonitor = WifiInjector.getInstance().getWifiMonitor();
String ifaceName = wifiNative.getClientInterfaceName();
if (TextUtils.isEmpty(ifaceName)) {
return null;
}
if (wifiNative.getBgScanCapabilities(
ifaceName, new WifiNative.ScanCapabilities())) {
return new HalWifiScannerImpl(context, ifaceName, wifiNative, wifiMonitor,
looper, clock);
} else {
return new WificondScannerImpl(context, ifaceName, wifiNative, wifiMonitor,
new WificondChannelHelper(wifiNative), looper, clock);
}
}
};
由于目前手边设备均是走的else
逻辑分支,即构造WificondScannerImpl
作为其实现,因此后面均以WificondScannerImpl
的逻辑为主;
WificondScannerImpl
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@Override
public boolean startSingleScan(WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
synchronized (mSettingsLock) {
//不允许多个扫描请求同时运行
if (mLastScanSettings != null) {
Log.w(TAG, "A single scan is already running");
return false;
}
//处理参数,合并所有需要扫描的频段(根据之前逻辑,single scan应该只有一个bucket,且为全频段)
ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
boolean reportFullResults = false;
for (int i = 0; i < settings.num_buckets; ++i) {
WifiNative.BucketSettings bucketSettings = settings.buckets[i];
if ((bucketSettings.report_events
& WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
reportFullResults = true;
}
allFreqs.addChannels(bucketSettings);
}
//如果有需要扫描的隐藏网络,在此处处理添加
Set<String> hiddenNetworkSSIDSet = new HashSet<>();
if (settings.hiddenNetworks != null) {
int numHiddenNetworks =
Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
for (int i = 0; i < numHiddenNetworks; i++) {
hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid);
}
}
//构造mLastScanSettings,会在扫描失败,或扫描成功,且扫描结果成功取回后置为null
mLastScanSettings = new LastScanSettings(
mClock.getElapsedSinceBootMillis(),
reportFullResults, allFreqs, eventHandler);
boolean success = false;
Set<Integer> freqs;
if (!allFreqs.isEmpty()) {
freqs = allFreqs.getScanFreqs();
//下发扫描请求
success = mWifiNative.scan(
mIfaceName, settings.scanType, freqs, hiddenNetworkSSIDSet);
...
} else {
...
}
if (success) {
...
//设置定时器,处理扫描耗时过长的情况
mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
@Override public void onAlarm() {
handleScanTimeout();
}
};
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
} else {
// indicate scan failure async
mEventHandler.post(new Runnable() {
@Override public void run() {
reportScanFailure();
}
});
}
return true;
}
}
这个方法内也是进行了参数处理、合并,以及异常处理,而正常逻辑主要在WifiNative.scan()
:
WifiNative
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java
public boolean scan(
@NonNull String ifaceName, int scanType, Set<Integer> freqs,
Set<String> hiddenNetworkSSIDs) {
return mWificondControl.scan(ifaceName, scanType, freqs, hiddenNetworkSSIDs);
}
WificondControl
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WificondControl.java
public boolean scan(@NonNull String ifaceName,
int scanType,
Set<Integer> freqs,
Set<String> hiddenNetworkSSIDs) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
...
try {
return scannerImpl.scan(settings);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request scan due to remote exception");
}
return false;
}
关于getScannerImpl()
:
/** Helper function to look up the scanner impl handle using name */
private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
return mWificondScanners.get(ifaceName);
}
这里简单介绍下mWificondScanners
这个数据结构怎么来的:
在打开WLAN时,会调用到WificondControl.setupInterfaceForClientMode()
:
/**
* Setup interface for client mode via wificond.
* @return An IClientInterface as wificond client interface binder handler.
* Returns null on failure.
*/
public IClientInterface setupInterfaceForClientMode(@NonNull String ifaceName) {
...
IClientInterface clientInterface = null;
try {
clientInterface = mWificond.createClientInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return null;
}
...
// Refresh Handlers
mClientInterfaces.put(ifaceName, clientInterface);
try {
IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
...
mWificondScanners.put(ifaceName, wificondScanner);
...
} catch (RemoteException e) {
Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
}
return clientInterface;
}
这里涉及到wificond
了,也就是说上面的IWifiScannerImpl.scan()
已经是由wificond
在负责完成了;
篇幅有限,这里我们就不继续深入了;
总结
以上是Android Framework层对于应用请求WLAN扫描的处理逻辑,主要是参数封装与合并,线程切换很多,并且也涉及到多个类的跳转;
整体来看,对于第一次了解的开发人员来看,是比较绕的;
但是梳理下来发现,大部分逻辑,都是针对一些特殊情况的处理(频繁重复请求)与优化(相同参数请求合并);
而对于有效的扫描请求,实际上逻辑分叉并不会多;
最后,老规矩,附上一张流程图: