您的位置:首页 > 移动开发 > Android开发

Android N Wi-Fi 扫描流程分析

2017-08-30 15:46 519 查看
上一篇我们分析了wifi的启动流程,最终 WifiStateMachine 是停留在了DisconnectedState 状态中。
接下来我们就来分析wifi是怎么进行扫描的。这就要从 WifiSettings 的 onResume 函数说起。


@Override
public void onResume() {
final Activity activity = getActivity();
super.onResume();
removePreference("dummy");
if (mWifiEnabler != null) {
mWifiEnabler.resume(activity);
}

mWifiTracker.startTracking();
activity.invalidateOptionsMenu();

/// M: register no WAPI certification action receiver
activity.registerReceiver(mReceiver, mFilter);
mWifiSettingsExt.onResume();
}


在这个函数中,执行了 WifiTracker.startTracking() 函数。


public void startTracking() {
resumeScanning();
if (!mRegistered) {
mContext.registerReceiver(mReceiver, mFilter);
// NetworkCallback objects cannot be reused. http://b/20701525 .
mNetworkCallback = new WifiTrackerNetworkCallback();
mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
mRegistered = true;
}
}

public void resumeScanning() {
if (mScanner == null) {
mScanner = new Scanner();
}

mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
if (mWifiManager.isWifiEnabled()) {
mScanner.resume();
}
mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
}


其中 resumeScanning() 函数发送了一条消息 orkHandler.MSG_RESUME 。


case MSG_RESUME:
handleResume();
break;

private void handleResume() {
mScanResultCache.clear();
mSeenBssids.clear();
mScanId = 0;
}


这个函数是为了清空当前 ScanResultCache 中的信息,紧接着将执行 mScanner.resume() 函数。


void resume() {
if (!hasMessages(MSG_SCAN)) {
sendEmptyMessage(MSG_SCAN);
}
}

@Override
public void handleMessage(Message message) {
if (message.what != MSG_SCAN) return;
if (mWifiManager.startScan()) {
mRetry = 0;
} else if (++mRetry >= 3) {
mRetry = 0;
if (mContext != null) {
Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
}
return;
}
sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
}


这个函数将先发送一条 MSG_SCAN 消息,然后将执行 mWifiManager.startScan() 函数。


public boolean startScan() {
try {
mService.startScan(null, null);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}


而 WifiManager 将通过aidl 调用 wifiserviceimpl 的同名函数。


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;
}
}
if (settings != null) {
settings = new ScanSettings(settings);
if (!settings.isValid()) {
Slog.e(TAG, "invalid scan setting");
return;
}
}
if (workSource != null) {
enforceWorkSourcePermission();
// WifiManager currently doesn't use names, so need to clear names out of the
// supplied WorkSource to allow future WorkSource combining.
workSource.clearNames();
}
if (workSource == null && Binder.getCallingUid() >= 0) {
workSource = new WorkSource(Binder.getCallingUid());
}
mWifiStateMachine.startScan(Binder.getCallingUid(), scanRequestCounter++,
settings, workSource);
}


此函数最后调用 WifiStateMachine.startScan()。


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);
}


此函数将往 WifiStateMachine 中发送 CMD_START_SCAN 消息。由于 DisconnectedState 状态不处理,
此消息将在 DriverStartedState 中被处理。


case CMD_START_SCAN:
handleScanRequest(message);
break;

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;
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

if (!mIsScanOngoing) {
// if rejection is NOT due to ongoing scan (e.g. bad scan parameters),

// discard this request and pop up the next one
if (mBufferedScanMsg.size() > 0) {
sendMessage(mBufferedScanMsg.remove());
}
messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
} else if (!mIsFullScanOngoing) {
// if rejection is due to an ongoing scan, and the ongoing one is NOT a full scan,
// buffer the scan request to make sure specified channels will be scanned eventually
if (freqs == null)
mBufferedScanMsg.clear();
if (mBufferedScanMsg.size() < SCAN_REQUEST_BUFFER_MAX_SIZE) {
Message msg = obtainMessage(CMD_START_SCAN,
message.arg1, message.arg2, bundle);
mBufferedScanMsg.add(msg);
} else {
// if too many requests in buffer, combine them into a single full scan
bundle = new Bundle();
bundle.putParcelable(CUSTOMIZED_SCAN_SETTING, null);
bundle.putParcelable(CUSTOMIZED_SCAN_WORKSOURCE, workSource);
Message msg = obtainMessage(CMD_START_SCAN, message.arg1, message.arg2, bundle);
mBufferedScanMsg.clear();
mBufferedScanMsg.add(msg);
}
messageHandlingStatus = MESSAGE_HANDLING_STATUS_LOOPED;
} else {
// mIsScanOngoing and mIsFullScanOngoing
messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
}
}


这里 startScanNative 函数将 调用 WifiScanner.startScan 函数。


public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
Log.d(TAG, "startScan, pid:" + Process.myPid() + ", tid:" + Process.myTid() + ", uid:" +
Process.myUid());
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);
}


此函数将发送 CMD_START_SINGLE_SCAN 消息到 WifiScanningServiceImpl 中实现。


case WifiScanner.CMD_START_SINGLE_SCAN:
case WifiScanner.CMD_STOP_SINGLE_SCAN:
mSingleScanStateMachine.sendMessage(Message.obtain(msg));
break;


最终将调用 mWifiNative.scan进行扫描网络热入点。扫描结束后,WifiMonitor.handleEvent 将收到SCAN_RESULTS 消息。


case SCAN_RESULTS:
sendMessage(iface, SCAN_RESULTS_EVENT);
break;


WifiMonitor 将向 WifiStateMachine 中发送 SCAN_RESULTS_EVENT 消息。
由于 DisconnectedState 状态不执行此消息,最终 由 SupplicantStartedState 状态来执行此消息。


case WifiMonitor.SCAN_RESULTS_EVENT:
case WifiMonitor.SCAN_FAILED_EVENT:
maybeRegisterNetworkFactory(); // Make sure our NetworkFactory is registered
setScanResults();
///M: for operator plugin @{
if (hasCustomizedAutoConnect()) {
mShowReselectDialog = false;
Log.d(TAG, "SCAN_RESULTS_EVENT, mScanForWeakSignal:" + mScanForWeakSignal);
if (mScanForWeakSignal) {
showReselectionDialog();
}
mDisconnectNetworkId = INVALID_NETWORK_ID;
}
///@}
loge("mIsFullScanOngoing: " + mIsFullScanOngoing
+ ", mSendScanResultsBroadcast: " + mSendScanResultsBroadcast);
///M: ALPS02028415 the last two times scan results should be broadcasted @{
if (mIsFullScanOngoing || mSendScanResultsBroadcast || mWifiOnScanCount < 2) {
loge("mWifiOnScanCount: " + mWifiOnScanCount);
/* Just updated results from full scan, let apps know a
c440
bout this */
boolean scanSucceeded = message.what == WifiMonitor.SCAN_RESULTS_EVENT;
sendScanResultsAvailableBroadcast(scanSucceeded);//发送扫描成功广播
}
mWifiOnScanCount ++;
///@}
mSendScanResultsBroadcast = false;
mIsScanOngoing = false;
mIsFullScanOngoing = false;
if (mBufferedScanMsg.size() > 0)
sendMessage(mBufferedScanMsg.remove());
break;


setScanResults()函数将会调用 WifiNative.getScanResults() 去获取扫描结果,
sendScanResultsAvailableBroadcast(scanSucceeded); 随后将发送
WifiManager.SCAN_RESULTS_AVAILABLE_ACTION 广播。
WifiTracker 接收到此广播后,将发送 MSG_UPDATE_ACCESS_POINTS 消息。


@VisibleForTesting
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
mConnected.set(info.isConnected());

mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);

mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
.sendToTarget();
}
}
};


WifiTracker 的 WorkHandler 接收到此消息将进行 更新AP操作。


private void updateAccessPoints() {
// Swap the current access points into a cached list.
List<AccessPoint> cachedAccessPoints = getAccessPoints();
ArrayList<AccessPoint> accessPoints = new ArrayList<>();

// Clear out the configs so we don't think something is saved when it isn't.
for (AccessPoint accessPoint : cachedAccessPoints) {
accessPoint.clearConfig();
}
/// M: ALPS02334170: Use ReadWriteLock to prevent multi-thread JE @{
mReadWriteLock.readLock().lock();
try {
/** Lookup table to more quickly update AccessPoints by only considering objects with
*  the correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
WifiConfiguration connectionConfig = null;
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
}

final Collection<ScanResult> results = fetchScanResults();

final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
mSavedNetworksExist = configs.size() != 0;
for (WifiConfiguration config : configs) {
if (config.selfAdded && config.numAssociation == 0) {
continue;
}
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
if (config.isPasspoint() == false) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
}
if (mIncludeSaved) {
if (!config.isPasspoint() || mIncludePasspoints) {
// If saved network not present in scan result then set its Rssi
// to MAX_VALUE
boolean apFound = false;
for (ScanResult result : results) {
if (result.SSID.equals(accessPoint.getSsidStr())) {
apFound = true;
break;
}
}
if (!apFound) {
accessPoint.setRssi(Integer.MAX_VALUE);
}
accessPoints.add(accessPoint);
}

if (config.isPasspoint() == false) {
apMap.put(accessPoint.getSsidStr(), accessPoint);
}
} else {
// If we aren't using saved networks, drop them into the cache so that
// we have access to their saved info.
cachedAccessPoints.add(accessPoint);
}
}
}

if (results != null) {
for (ScanResult result : results) {
// Ignore hidden and ad-hoc networks.
if (result.SSID == null || result.SSID.length() == 0 ||
result.capabilities.contains("[IBSS]")) {
continue;
}

boolean found = false;
for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
if (accessPoint.update(result)) {
found = true;
break;
}
}
if (!found && mIncludeScans) {
AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}

if (result.isPasspointNetwork()) {
WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
if (config != null) {
accessPoint.update(config);
}
}

if (mLastInfo != null && mLastInfo.getBSSID() != null
&& mLastInfo.getBSSID().equals(result.BSSID)
&& connectionConfig != null && connectionConfig.isPasspoint()) {
/* This network is connected via this passpoint config */
/* SSID match is not going to work for it; so update explicitly */
accessPoint.update(connectionConfig);
}

accessPoints.add(accessPoint);
apMap.put(accessPoint.getSsidStr(), accessPoint);
}
}
}
} finally {
mReadWriteLock.readLock().unlock();
}
/// @}

// Pre-sort accessPoints to speed preference insertion
Collections.sort(accessPoints);

// Log accesspoints that were deleted
if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
for (AccessPoint prevAccessPoint : mAccessPoints) {
if (prevAccessPoint.getSsid() == null) continue;
String prevSsid = prevAccessPoint.getSsidStr();
boolean found = false;
for (AccessPoint newAccessPoint : accessPoints) {
if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) {
found = true;
break;
}
}
if (!found)
if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan");
}
if (DBG)  Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");

mAccessPoints = accessPoints;
mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
}


此函数将会把扫描到的AP信息更新到 WifiSetting 中的wifi列表里。至此整个wifi扫描流程就结束了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android wi-fi