最全面的Android Wifi扫描分析

您所在的位置:网站首页 joyu是什么意思 最全面的Android Wifi扫描分析

最全面的Android Wifi扫描分析

2024-01-04 00:47| 来源: 网络整理| 查看: 265

使用

开始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 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 == 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; } .... } private boolean startScanNative(final Set freqs, Set hiddenNetworkIds, WorkSource workSource) { ... 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(settings, nativeScanListener, workSource); ... }

WifiScanner中startScan方法,通过AsyncChannel中的Messenger将message发送到WifiScanningServiceImpl中 mWifiScanner.startScan最终调用的是WifiScanningServiceImpl中:

public class WifiScanningServiceImpl extends IWifiScanner.Stub { ... class DriverStartedState extends State { @Override public boolean processMessage(Message msg) { case WifiScanner.CMD_START_SINGLE_SCAN: if (validateScanRequest(ci, handler, scanSettings)) { ... replySucceeded(msg); // If there is an active scan that will fulfill the scan request then // mark this request as an active scan, otherwise mark it pending. // 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. 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 { ... } } } void tryToStartNewScan() { ... if (mScannerImpl.startSingleScan(settings, this)) { ... } else { .... } } ... } 如果在DefaultState状态下接受到scan请求,该次扫描失败。 如果在ScanningState状态下接受到scan请求: 如果当前正在进行的扫描能满足需求,将请求加入active队列,否则加入挂起队列 如果是其他状态直接加入挂起队列,并立即调用tryToStartNewScan() mScannerImpl通过工厂方法生成的实例为WificondScannerImpl,在WificondScannerImpl中startSingleScan: @Override public boolean startSingleScan(WifiNative.ScanSettings settings, WifiNative.ScanEventHandler eventHandler) { synchronized (mSettingsLock) { ... if (!allFreqs.isEmpty()) { freqs = allFreqs.getScanFreqs(); success = mWifiNative.scan( mIfaceName, settings.scanType, freqs, hiddenNetworkSSIDSet); if (!success) { Log.e(TAG, "Failed to start scan, freqs=" + freqs); } } else { // There is a scan request but no available channels could be scanned for. // We regard it as a scan failure in this case. Log.e(TAG, "Failed to start scan because there is no available channel to scan"); } 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 { ... } return true; } }

可见在调用了mWifiNative.scan后,还设置了timeout机制,交给AlarmManager去执行 WifiNative中的scan调用的是WificondControl中的scan方法,我们看下WificondControl中:

public boolean scan(@NonNull String ifaceName, int scanType, Set freqs, List 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; } private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) { return mWificondScanners.get(ifaceName); }

mWificondScanners是个hashmap,数据在 setupInterfaceForClientMode方法中put进去:

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; } ... try { IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl(); if (wificondScanner == null) { Log.e(TAG, "Failed to get WificondScannerImpl"); return null; } mWificondScanners.put(ifaceName, wificondScanner); ... } catch (RemoteException e) { Log.e(TAG, "Failed to refresh wificond scanner due to remote exception"); } return clientInterface; }

mWificond的类型为IWificond,clientInterface的类型为IClientInterface,这两个都是aidl生成的接口,具体的实现在IWificond.cpp中,cpp中的内容此处就不做深入

2. Android 8.0,8.1系统:

WifiServiceImpl类中:

@Override public void startScan(ScanSettings settings, WorkSource workSource, String packageName) { enforceChangePermission(); mLog.trace("startScan uid=%").c(Binder.getCallingUid()).flush(); // Check and throttle background apps for wifi scan. if (isRequestFromBackground(packageName)) { long lastScanMs = mLastScanTimestamps.getOrDefault(packageName, 0L); long elapsedRealtime = mClock.getElapsedSinceBootMillis(); if (lastScanMs != 0 && (elapsedRealtime - lastScanMs) < mBackgroundThrottleInterval) { sendFailedScanBroadcast(); return; } // Proceed with the scan request and record the time. mLastScanTimestamps.put(packageName, elapsedRealtime); } synchronized (this) { if (mWifiScanner == null) { mWifiScanner = mWifiInjector.getWifiScanner(); } if (mInIdleMode) { // Need to send an immediate scan result broadcast in case the // caller is waiting for a result .. // TODO: investigate if the logic to cancel scans when idle can move to // WifiScanningServiceImpl. This will 1 - clean up WifiServiceImpl and 2 - // avoid plumbing an awkward path to report a cancelled/failed scan. This will // be sent directly until b/31398592 is fixed. sendFailedScanBroadcast(); mScanPending = true; return; } } ... mWifiStateMachine.startScan(Binder.getCallingUid(), scanRequestCounter++, settings, workSource); }

可见多了一个isRequestFromBackground操作,如果是background进程的调用,距上次调用< mBackgroundThrottleInterval,则sendFailedScanBroadcast mBackgroundThrottleInterval参数是通过读取framework中的设置,这个值默认为30分钟

private void updateBackgroundThrottleInterval() { mBackgroundThrottleInterval = mFrameworkFacade.getLongSetting( mContext, Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS); } 3. Android 9.0系统:

WifiServiceImpl类中:

@Override public boolean startScan(String packageName) { if (enforceChangePermission(packageName) != MODE_ALLOWED) { return false; } int callingUid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); mLog.info("startScan uid=%").c(callingUid).flush(); synchronized (this) { if (mInIdleMode) { // Need to send an immediate scan result broadcast in case the // caller is waiting for a result .. // TODO: investigate if the logic to cancel scans when idle can move to // WifiScanningServiceImpl. This will 1 - clean up WifiServiceImpl and 2 - // avoid plumbing an awkward path to report a cancelled/failed scan. This will // be sent directly until b/31398592 is fixed. sendFailedScanBroadcast(); mScanPending = true; return false; } } try { mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, callingUid); Mutable scanSuccess = new Mutable(); 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; } if (!scanSuccess.value) { Log.e(TAG, "Failed to start scan"); return false; } } catch (SecurityException e) { return false; } finally { Binder.restoreCallingIdentity(ident); } return true; }

mWifiPermissionsUtil.enforceCanAccessScanResults ,permission判断对比8.0有更新,WifiPermissionsUtil会判断各种所需权限 scan交给mScanRequestProxy去做:

public boolean startScan(int callingUid, String packageName) { if (!retrieveWifiScannerIfNecessary()) { Log.e(TAG, "Failed to retrieve wifiscanner"); sendScanResultFailureBroadcastToPackage(packageName); return false; } boolean fromSettingsOrSetupWizard = mWifiPermissionsUtil.checkNetworkSettingsPermission(callingUid) || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(callingUid); // Check and throttle scan request from apps without NETWORK_SETTINGS permission. if (!fromSettingsOrSetupWizard && shouldScanRequestBeThrottledForApp(callingUid, packageName)) { Log.i(TAG, "Scan request from " + packageName + " throttled"); sendScanResultFailureBroadcastToPackage(packageName); return false; } // Create a worksource using the caller's UID. WorkSource workSource = new WorkSource(callingUid); // Create the scan settings. WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings(); // Scan requests from apps with network settings will be of high accuracy type. if (fromSettingsOrSetupWizard) { settings.type = WifiScanner.TYPE_HIGH_ACCURACY; } // always do full scans settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT; if (mScanningForHiddenNetworksEnabled) { // retrieve the list of hidden network SSIDs to scan for, if enabled. List hiddenNetworkList = mWifiConfigManager.retrieveHiddenNetworkList(); settings.hiddenNetworks = hiddenNetworkList.toArray( new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]); } mWifiScanner.startScan(settings, new ScanRequestProxyScanListener(), workSource); mIsScanProcessingComplete = false; return true; return true; } 这里直接交给WifiScanner去执行,相比于之前版本省去了WifiStateMachine状态管理 如果一个应用拥有networksetting权限(就是android中的设置才有的权限,一般应用不可能有)则可以不受任何限制地扫描,如果没有这个权限,这Wi-Fi扫描将会执行shouldScanRequestBeThrottledForApp,如果返回为ture,则sendScanResultFailureBroadcastToPackage 看下shouldScanRequestBeThrottledForApp做了什么: private boolean shouldScanRequestBeThrottledForApp(int callingUid, String packageName) { boolean isThrottled; if (isRequestFromBackground(callingUid, packageName)) { isThrottled = shouldScanRequestBeThrottledForBackgroundApp(); if (isThrottled) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Background scan app request [" + callingUid + ", " + packageName + "]"); } mWifiMetrics.incrementExternalBackgroundAppOneshotScanRequestsThrottledCount(); } } else { isThrottled = shouldScanRequestBeThrottledForForegroundApp(callingUid, packageName); if (isThrottled) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Foreground scan app request [" + callingUid + ", " + packageName + "]"); } mWifiMetrics.incrementExternalForegroundAppOneshotScanRequestsThrottledCount(); } } mWifiMetrics.incrementExternalAppOneshotScanRequestsCount(); return isThrottled; } private boolean shouldScanRequestBeThrottledForBackgroundApp() { long lastScanMs = mLastScanTimestampForBgApps; long elapsedRealtime = mClock.getElapsedSinceBootMillis(); if (lastScanMs != 0 && (elapsedRealtime - lastScanMs) < SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS) { return true; } // Proceed with the scan request and record the time. mLastScanTimestampForBgApps = elapsedRealtime; return false; } private boolean shouldScanRequestBeThrottledForForegroundApp( int callingUid, String packageName) { LinkedList scanRequestTimestamps = getOrCreateScanRequestTimestampsForForegroundApp(callingUid, packageName); long currentTimeMillis = mClock.getElapsedSinceBootMillis(); // First evict old entries from the list. trimPastScanRequestTimesForForegroundApp(scanRequestTimestamps, currentTimeMillis); if (scanRequestTimestamps.size() >= SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS) { return true; } // Proceed with the scan request and record the time. scanRequestTimestamps.addLast(currentTimeMillis); return false; } 判断应用为前台应该还是后台应用(前后台限制在这里) 后台应用限制SCAN_REQUEST_THROTTLE_INTERVAL_BG_APPS_MS未30分钟 前台应用方法获取了一个保存每次扫描时间戳的链表scanRequestTimestamps ,每个应用又以不同的链表uid为键将自己的链表保存在一个设备全局的map中,每次调用这个方法将移除这个链表2分钟以前的时间戳,如果移除以后,这个链表仍然大于4,则取消本次扫描,否则将当前时间戳加入链表。 3. Android 10.0系统:

和Android 9相比,Android10 在enforceCanAccessScanResults 中作了更多判断:

public class WifiPermissionsUtil { public void enforceCanAccessScanResults(String pkgName, int uid) throws SecurityException { checkPackage(uid, pkgName); // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_MANAGED_PROVISIONING, // NETWORK_STACK & MAINLINE_NETWORK_STACK are granted a bypass. if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) || checkNetworkManagedProvisioningPermission(uid) || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) { return; } // Location mode must be enabled if (!isLocationModeEnabled()) { // Location mode is disabled, scan results cannot be returned throw new SecurityException("Location mode is disabled for the device"); } // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS permission. boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid); // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to // location information. boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, uid, /* coarseForTargetSdkLessThanQ */ true); // If neither caller or app has location access, there is no need to check // any other permissions. Deny access to scan results. if (!canCallingUidAccessLocation && !canAppPackageUseLocation) { throw new SecurityException("UID " + uid + " has no location permission"); } // Check if Wifi Scan request is an operation allowed for this App. if (!isScanAllowedbyApps(pkgName, uid)) { throw new SecurityException("UID " + uid + " has no wifi scan permission"); } // If the User or profile is current, permission is granted // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) { throw new SecurityException("UID " + uid + " profile not permitted"); } } }

多了checkNetworkManagedProvisioningPermission,checkNetworkStackPermission和checkMainlineNetworkStackPermission

总结 WifiManager通过aidl调用WifiServiceImpl,由app进程发送到系统进程 在scan之前会检查permission 9.0以下通过WifiStateMachine管理状态,9.0以上通过ScanRequestProxy代理,并交于WifiScanner WifiScanner通过AsyncChannel中的Messenger将message发送到WifiScanningServiceImpl中 WifiScanningServiceImpl自身也有状态管理,mScannerImpl调用scan后,启动了超时机制

最终由IWificond.aidl实现者IWificond.cpp实现scan操作 整理了下流程图:

sequence_diagram.png getScanResults

WifiServiceImpl中:

@Override public List getScanResults(String callingPackage) { enforceAccessPermission(); ... try { mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, uid); final List scanResults = new ArrayList(); boolean success = mWifiInjector.getClientModeImplHandler().runWithScissors(() -> { scanResults.addAll(mScanRequestProxy.getScanResults()); }, RUN_WITH_SCISSORS_TIMEOUT_MILLIS); if (!success) { Log.e(TAG, "Failed to post runnable to fetch scan results"); return new ArrayList(); } return scanResults; } catch (SecurityException e) { Slog.e(TAG, "Permission violation - getScanResults not allowed for uid=" + uid + ", packageName=" + callingPackage + ", reason=" + e); return new ArrayList(); } finally { Binder.restoreCallingIdentity(ident); } }

也做了permisson的判断 ScanRequestProxy中的mLastScanResults是如何set的呢:

private class GlobalScanListener implements WifiScanner.ScanListener { @Override public void onResults(WifiScanner.ScanData[] scanDatas) { ... if (scanData.getBandScanned() == WifiScanner.WIFI_BAND_BOTH_WITH_DFS) { // Store the last scan results & send out the scan completion broadcast. mLastScanResults.clear(); mLastScanResults.addAll(Arrays.asList(scanResults)); sendScanResultBroadcast(true); } } } private boolean retrieveWifiScannerIfNecessary() { if (mWifiScanner == null) { mWifiScanner = mWifiInjector.getWifiScanner(); // Start listening for throttle settings change after we retrieve scanner instance. mThrottleEnabledSettingObserver.initialize(); // Register the global scan listener. if (mWifiScanner != null) { mWifiScanner.registerScanListener(new GlobalScanListener()); } } return mWifiScanner != null; }

在WifiScanningServiceImpl 里注册了一个监听器 并将此接口加入到mSingleScanListeners中:

case WifiScanner.CMD_REGISTER_SCAN_LISTENER: logScanRequest("registerScanListener", ci, msg.arg2, null, null, null); mSingleScanListeners.addRequest(ci, msg.arg2, null, null); replySucceeded(msg); break;

在方法reportScanResults中会遍历mSingleScanListeners:

void reportScanResults(ScanData results) { ... for (RequestInfo entry : mSingleScanListeners) { logCallback("singleScanResults", entry.clientInfo, entry.handlerId, describeForLog(allResults)); entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults); } ... }

当调用WifiScanner.getScanResults时会发送WifiScanner.CMD_GET_SCAN_RESULTS的message,当接收到WifiScanner.CMD_GET_SCAN_RESULTS的message时,会调用reportScanResults方法



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3