使用
开始wifi扫描的代码很简单:
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val success = wifiManager.startScan()
if (!success) {
// scan failure handling
scanFailure()
}
然后定义一个receiver接收结果
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
if (success) {
val results = wifiManager.scanResults
} else {
scanFailure()
}
}
}
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)
注意:scanFailure时,wifiManager.scanResults的数据未上一次的扫描结果
版本差异
Android 8以下:
未限制
Android 8.0 和 Android 8.1:
每个后台应用可以在 30 分钟内扫描一次。
需要申明以下任意一项权限即可:
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
CHANGE_WIFI_STATE
Android 9:
每个前台应用可以在 2 分钟内扫描四次。这样便可在短时间内进行多次扫描。
所有后台应用组合可以在 30 分钟内扫描一次。
需要申明以下所有权限:
ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
CHANGE_WIFI_STATE
设备已启用位置服务 (Settings > Location)。
Android 10 及更高版本:
用 Android 9 的节流限制。新增一个开发者选项,用户可以关闭节流功能以便进行本地测试(Developer Options > Networking > Wi-Fi scan throttling)
target>=29,必须有 ACCESS_FINE_LOCATION
target<29,ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION都可以
CHANGE_WIFI_STATE
设备已启用位置服务 (Settings > Location)。
源码解析
startScan
WifiManager类中的startScan方法:
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
public boolean startScan(WorkSource workSource) {
try {
String packageName = mContext.getOpPackageName();
mService.startScan(null, workSource, packageName);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
最终通过IWifiManager.aidl,调用的是WifiServiceImpl类
不同系统版本有不同实现
1. Android 6.0,7.0系统:
public void startScan(ScanSettings settings, WorkSource workSource) {
enforceChangePermission();
synchronized (this) {
if (mInIdleMode) {
// Need to send an immediate scan result broadcast in case the
// caller is waiting for a result ..
// clear calling identity to send broadcast
long callingIdentity = Binder.clearCallingIdentity();
try {
mWifiStateMachine.sendScanResultsAvailableBroadcast(/* scanSucceeded = */ false);
} finally {
// restore calling identity
Binder.restoreCallingIdentity(callingIdentity);
}
mScanPending = true;
return;
}
}
...
mWifiStateMachine.startScan(Binder.getCallingUid(), scanRequestCounter++,
settings, workSource);
}
enforceChangePermission是检查是否有CHANGE_WIFI_STATE的权限
mInIdleMode由powermanager判定设备是否处于空闲状态
如果处于空闲,则不再真正扫描,而是调用WifiStateMachine发送最近可用的扫描结果
我们看下WifiStateMachine的代码:
/**
* Track the state of Wifi connectivity. All event handling is done here,
* and all changes in connectivity state are initiated here.
*
* Wi-Fi now supports three modes of operation: Client, SoftAp and p2p
* In the current implementation, we support concurrent wifi p2p and wifi operation.
* The WifiStateMachine handles SoftAp and Client operations while WifiP2pService
* handles p2p operation.
*
* @hide
*/
public class WifiStateMachine extends StateMachine implements WifiNative.WifiRssiEventHandler {
...
public void startScan(int callingUid, int scanCounter,
ScanSettings settings, WorkSource workSource) {
Bundle bundle = new Bundle();
bundle.putParcelable(CUSTOMIZED_SCAN_SETTING, settings);
bundle.putParcelable(CUSTOMIZED_SCAN_WORKSOURCE, workSource);
bundle.putLong(SCAN_REQUEST_TIME, System.currentTimeMillis());
sendMessage(CMD_START_SCAN, callingUid, scanCounter, bundle);
}
...
}
这个类主要维护Wifi连接的各种状态,以及所有事件的处理
其中维护了ScanModeState,DriverStartedState,DriverStartingState,ConnectModeState等等
在ScanModeState中的processMessage方法调用了handleScanRequest方法:
class ScanModeState extends State {
...
@Override
public boolean processMessage(Message message) {
// Handle scan. All the connection related commands are
// handled only in ConnectModeState
case CMD_START_SCAN:
handleScanRequest(message);
break;
}
}
private void handleScanRequest(Message message) {
...
// call wifi native to start the scan
if (startScanNative(freqs, hiddenNetworkIds, workSource)) {
// a full scan covers everything, clearing scan request buffer
if (freqs == nu