Android N Wi-Fi 扫描流程分析

上一篇我们分析了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 about 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扫描流程就结束了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值