最全面的Android Wifi掃描分析

使用

開始wifi掃描的代碼很簡單:

val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val success = wifiManager.startScan()
if (!success) {
  // scan failure handling
  scanFailure()
}

然后定義一個receiver接收結(jié)果

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的數(shù)據(jù)未上一次的掃描結(jié)果

版本差異

  • Android 8以下:
    未限制

  • Android 8.0 和 Android 8.1:
    每個后臺應用可以在 30 分鐘內(nèi)掃描一次驻襟。
    需要申明以下任意一項權限即可:
    ACCESS_FINE_LOCATION
    ACCESS_COARSE_LOCATION
    CHANGE_WIFI_STATE

  • Android 9:
    每個前臺應用可以在 2 分鐘內(nèi)掃描四次告组。這樣便可在短時間內(nèi)進行多次掃描抒抬。
    所有后臺應用組合可以在 30 分鐘內(nèi)掃描一次置侍。
    需要申明以下所有權限:
    ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
    CHANGE_WIFI_STATE
    設備已啟用位置服務 (Settings > Location)。

  • Android 10 及更高版本:
    用 Android 9 的節(jié)流限制衰伯。新增一個開發(fā)者選項铡羡,用戶可以關閉節(jié)流功能以便進行本地測試(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尽爆,調(diào)用的是WifiServiceImpl類
不同系統(tǒng)版本有不同實現(xiàn)

1. Android 6.0,7.0系統(tǒng):

 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判定設備是否處于空閑狀態(tài)
如果處于空閑,則不再真正掃描读慎,而是調(diào)用WifiStateMachine發(fā)送最近可用的掃描結(jié)果

我們看下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連接的各種狀態(tài)漱贱,以及所有事件的處理
其中維護了ScanModeState,DriverStartedState夭委,DriverStartingState,ConnectModeState等等
在ScanModeState中的processMessage方法調(diào)用了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<Integer> freqs, Set<Integer> 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發(fā)送到WifiScanningServiceImpl中
mWifiScanner.startScan最終調(diào)用的是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狀態(tài)下接受到scan請求,該次掃描失敗株灸。
  • 如果在ScanningState狀態(tài)下接受到scan請求: 如果當前正在進行的掃描能滿足需求崇摄,將請求加入active隊列,否則加入掛起隊列
  • 如果是其他狀態(tài)直接加入掛起隊列慌烧,并立即調(diào)用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;
        }
    }

可見在調(diào)用了mWifiNative.scan后,還設置了timeout機制屹蚊,交給AlarmManager去執(zhí)行
WifiNative中的scan調(diào)用的是WificondControl中的scan方法厕氨,我們看下WificondControl中:

      public boolean scan(@NonNull String ifaceName,
                        int scanType,
                        Set<Integer> freqs,
                        List<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;
    }

    private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
        return mWificondScanners.get(ifaceName);
    }

mWificondScanners是個hashmap,數(shù)據(jù)在 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生成的接口,具體的實現(xiàn)在IWificond.cpp中嘱兼,cpp中的內(nèi)容此處就不做深入

2. Android 8.0,8.1系統(tǒng):

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進程的調(diào)用,距上次調(diào)用< mBackgroundThrottleInterval遭京,則sendFailedScanBroadcast
mBackgroundThrottleInterval參數(shù)是通過讀取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系統(tǒng):

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<Boolean> 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<WifiScanner.ScanSettings.HiddenNetwork> 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去執(zhí)行哪雕,相比于之前版本省去了WifiStateMachine狀態(tài)管理
  • 如果一個應用擁有networksetting權限(就是android中的設置才有的權限船殉,一般應用不可能有)則可以不受任何限制地掃描,如果沒有這個權限斯嚎,這Wi-Fi掃描將會執(zhí)行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<Long> 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中糠惫,每次調(diào)用這個方法將移除這個鏈表2分鐘以前的時間戳,如果移除以后钉疫,這個鏈表仍然大于4硼讽,則取消本次掃描,否則將當前時間戳加入鏈表牲阁。

3. Android 10.0系統(tǒng):

和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

總結(jié)

  • WifiManager通過aidl調(diào)用WifiServiceImpl,由app進程發(fā)送到系統(tǒng)進程
  • 在scan之前會檢查permission
  • 9.0以下通過WifiStateMachine管理狀態(tài)备燃,9.0以上通過ScanRequestProxy代理碉克,并交于WifiScanner
  • WifiScanner通過AsyncChannel中的Messenger將message發(fā)送到WifiScanningServiceImpl中
  • WifiScanningServiceImpl自身也有狀態(tài)管理,mScannerImpl調(diào)用scan后并齐,啟動了超時機制
  • 最終由IWificond.aidl實現(xiàn)者IWificond.cpp實現(xiàn)scan操作
    整理了下流程圖:


    sequence_diagram.png

getScanResults

WifiServiceImpl中:

@Override
    public List<ScanResult> getScanResults(String callingPackage) {
        enforceAccessPermission();
       ...
        try {
            mWifiPermissionsUtil.enforceCanAccessScanResults(callingPackage, uid);
            final List<ScanResult> 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<ScanResult>();
            }
            return scanResults;
        } catch (SecurityException e) {
            Slog.e(TAG, "Permission violation - getScanResults not allowed for uid="
                    + uid + ", packageName=" + callingPackage + ", reason=" + e);
            return new ArrayList<ScanResult>();
        } 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 里注冊了一個監(jiān)聽器
并將此接口加入到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<Void> entry : mSingleScanListeners) {
                logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
                        describeForLog(allResults));
                entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
            }
            ...
         }

當調(diào)用WifiScanner.getScanResults時會發(fā)送WifiScanner.CMD_GET_SCAN_RESULTS的message漏麦,當接收到WifiScanner.CMD_GET_SCAN_RESULTS的message時,會調(diào)用reportScanResults方法

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末况褪,一起剝皮案震驚了整個濱河市撕贞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌测垛,老刑警劉巖麻掸,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赐纱,居然都是意外死亡,警方通過查閱死者的電腦和手機熬北,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門疙描,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讶隐,你說我怎么就攤上這事起胰。” “怎么了巫延?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵效五,是天一觀的道長。 經(jīng)常有香客問我炉峰,道長畏妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任疼阔,我火速辦了婚禮戒劫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婆廊。我一直安慰自己迅细,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布淘邻。 她就那樣靜靜地躺著茵典,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宾舅。 梳的紋絲不亂的頭發(fā)上统阿,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天彩倚,我揣著相機與錄音,去河邊找鬼砂吞。 笑死署恍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜻直。 我是一名探鬼主播盯质,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼概而!你這毒婦竟也來了呼巷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赎瑰,失蹤者是張志新(化名)和其女友劉穎王悍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體餐曼,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡压储,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了源譬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片集惋。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖踩娘,靈堂內(nèi)的尸體忽然破棺而出刮刑,到底是詐尸還是另有隱情,我是刑警寧澤养渴,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布雷绢,位于F島的核電站,受9級特大地震影響理卑,放射性物質(zhì)發(fā)生泄漏翘紊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一傻工、第九天 我趴在偏房一處隱蔽的房頂上張望霞溪。 院中可真熱鬧,春花似錦中捆、人聲如沸鸯匹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殴蓬。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間染厅,已是汗流浹背痘绎。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肖粮,地道東北人孤页。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像涩馆,于是被迫代替她去往敵國和親行施。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349