【安卓Framework学习】Wifi框架学习之扫描模式及扫描流程

系列文章目录

【安卓Framework学习】Wifi框架学习之核心类.
【安卓Framework学习】Wifi框架学习之wifi状态机.
【安卓Framework学习】Wifi框架学习之开启与关闭流程.
【安卓Framework学习】Wifi框架学习之连接与断开流程.
【安卓Framework学习】Wifi框架学习之热点评分机制.
【安卓Framework学习】安卓连接管理(ConnectivityService)之wifi连接及注册.



前言

之前有分析学习过wifi的开启【安卓Framework学习】Wifi框架学习之开启与关闭流程和连接过程【安卓Framework学习】Wifi框架学习之连接与断开流程,但在没有连接的时候wifi模组又是如何扫描运作的呢?本篇将从wifi的扫描过程中分析其源码,并分析wifi扫描模式的不同。本篇代码主要基于安卓11源码进行分析。


一、wifi扫描流程(单次扫描)

1、wifi扫描源码流程分析

在wifi框架中,触发扫描的地方有很多,比如刚开启wifi就会触发扫描,所以这里只考虑从WifiManager调用下去的扫描流程。依旧是从上层调用扫描代码开始。

	public boolean startScan() {
	    return startScan(null);
	}
	
	public boolean startScan(WorkSource workSource) {
	    try {
	        String packageName = mContext.getOpPackageName();
	        String attributionTag = mContext.getAttributionTag();
	        return mService.startScan(packageName, attributionTag);
	    } catch (RemoteException e) {
	        throw e.rethrowFromSystemServer();
	    }
	}

直接调用到系统服务中的WifiServiceImpl.startScan.

	public boolean startScan(String packageName, String featureId) {
	    if (enforceChangePermission(packageName) != MODE_ALLOWED) {
	        return false;
	    }
	    int callingUid = Binder.getCallingUid();
	    long ident = Binder.clearCallingIdentity();
	    /*省略部分代码*/
	    try {
	        mWifiPermissionsUtil.enforceCanAccessScanResults(packageName, featureId, callingUid,
	                null);
	        Boolean scanSuccess = mWifiThreadRunner.call(() ->
	                mScanRequestProxy.startScan(callingUid, packageName), null);
	        if (scanSuccess == null) {
	            sendFailedScanBroadcast();
	            return false;
	        }
	        if (!scanSuccess) {
	            Log.e(TAG, "Failed to start scan");
	            return false;
	        }
	    } /*省略部分代码*/
	    return true;
	}

其中mWifiThreadRunner对象就是一个通过Handler对象执行任务的一个类,那么这里调用到了ScanRequestProxy.startScan.

	public boolean startScan(int callingUid, String packageName) {
	   /*省略部分代码*/
	    WorkSource workSource = new WorkSource(callingUid, packageName);
	    // 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.SCAN_TYPE_HIGH_ACCURACY;
	    }
	    // always do full scans
	    settings.band = WifiScanner.WIFI_BAND_ALL;
	    settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
	            | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
	    if (mScanningForHiddenNetworksEnabled) {
	        settings.hiddenNetworks.clear();
	        // retrieve the list of hidden network SSIDs from saved network to scan for, if enabled.
	        settings.hiddenNetworks.addAll(mWifiConfigManager.retrieveHiddenNetworkList());
	        // retrieve the list of hidden network SSIDs from Network suggestion to scan for.
	        settings.hiddenNetworks.addAll(
	                mWifiInjector.getWifiNetworkSuggestionsManager().retrieveHiddenNetworkList());
	    }
	    mWifiScanner.startScan(settings, new HandlerExecutor(mHandler),
	            new ScanRequestProxyScanListener(), workSource);
	    return true;
	}

前面设置了一些扫描必要的内容和参数后,调用了WifiScanner.startScan,这个是和WifiManager一样的一个代理类。

	public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
	        ScanListener listener, WorkSource workSource) {
	    Objects.requireNonNull(listener, "listener cannot be null");
	    int key = addListener(listener, executor);
	    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);
	    scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
	    scanParams.putString(REQUEST_FEATURE_ID_KEY, mContext.getAttributionTag());
	    mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
	}

这里用到了异步通道AsyncChannel跨进程与系统服务进行跨进程通信。这里不赘述连接过程,直接得出结论,跨进程通信的系统服务是WifiScannerServiceImplIWifiScanner的实现类。在WifiScannerServiceImpl中对异步通道过来的消息是由其内部类ClientHandler来处理,所以看ClientHandler.handleMessage.

	public void handleMessage(Message msg) {
		/*省略部分代码*/
		switch (msg.what) {
		    /*省略部分代码*/
		    case WifiScanner.CMD_START_SINGLE_SCAN:
		    case WifiScanner.CMD_STOP_SINGLE_SCAN:
		        mSingleScanStateMachine.sendMessage(Message.obtain(msg));
		        break;
		    /*省略部分代码*/
		}
	}

ClientHandler在收到CMD_START_SINGLE_SCAN消息后直接给WifiScannerServiceImpl内部状态机WifiSingleScanStateMachine发送了消息,状态机这里之前有详细分析过,直接看DriverStartedState对消息的处理。

	public boolean processMessage(Message msg) {
	    ClientInfo ci = mClients.get(msg.replyTo);
	    switch (msg.what) {
	    	/*省略部分代码*/
		    case WifiScanner.CMD_START_SINGLE_SCAN:
			    int handler = msg.arg2;
			    Bundle scanParams = (Bundle) msg.obj;
			    /*省略部分代码*/
			    if (validateScanRequest(ci, handler, scanSettings)) {
			        /*省略部分代码*/
			        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();
			        }
			    } /*省略部分代码*/
			    return HANDLED;
			    /*省略部分代码*/
	    }
	}

这里简单说明一下扫描过程中,WifiSingleScanStateMachine状态机中状态的切换其实就是在ScanningStateIdleState之间切换。在向下发送扫描指令后,会切换到ScanningState,在接收到有扫描结果的消息后会切换到IdleState,所以其实不管当前状态是否为ScanningState,都会调用到tryToStartNewScan方法,这个方法才是真正开启扫描的方法。直接看tryToStartNewScan方法。

	void tryToStartNewScan() {
	    if (mPendingScans.size() == 0) { // no pending requests
	        return;
	    }
	    mChannelHelper.updateChannels();
	    // TODO move merging logic to a scheduler
	    WifiNative.ScanSettings settings = new WifiNative.ScanSettings();
	    settings.num_buckets = 1;
	    WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
	    bucketSettings.bucket = 0;
	    bucketSettings.period_ms = 0;
	    bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
	    ChannelCollection channels = mChannelHelper.createChannelCollection();
	    List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
	    for (RequestInfo<ScanSettings> entry : mPendingScans) {
	        settings.scanType = mergeScanTypes(settings.scanType, entry.settings.type);
	        channels.addChannels(entry.settings);
	        for (ScanSettings.HiddenNetwork srcNetwork : entry.settings.hiddenNetworks) {
	            WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork();
	            hiddenNetwork.ssid = srcNetwork.ssid;
	            hiddenNetworkList.add(hiddenNetwork);
	        }
	        if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
	                != 0) {
	            bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
	        }
	    }
	    if (hiddenNetworkList.size() > 0) {
	        settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()];
	        int numHiddenNetworks = 0;
	        for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) {
	            settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork;
	        }
	    }
	    channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
	    settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
	    if (mScannerImplsTracker.startSingleScan(settings)) {
	        // store the active scan settings
	        mActiveScanSettings = settings;
	        // swap pending and active scan requests
	        RequestList<ScanSettings> tmp = mActiveScans;
	        mActiveScans = mPendingScans;
	        mPendingScans = tmp;
	        // make sure that the pending list is clear
	        mPendingScans.clear();
	        transitionTo(mScanningState);
	    } else {
	        mWifiMetrics.incrementScanReturnEntry(
	                WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size());
	        // notify and cancel failed scans
	        sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
	                "Failed to start single scan");
	    }
	}

这个方法中,前面部分其实就是设置一些扫描的信道个数,扫描周期,和隐藏列表等,后面主要的是调用ScannerImplsTracker.startSingleScan往下走进行扫描的。

	public boolean startSingleScan(WifiNative.ScanSettings scanSettings) {
	    mStatusPerImpl.clear();
	    boolean anySuccess = false;
	    for (Map.Entry<String, WifiScannerImpl> entry : mScannerImpls.entrySet()) {
	        String ifaceName = entry.getKey();
	        WifiScannerImpl impl = entry.getValue();
	        boolean success = impl.startSingleScan(
	                scanSettings, new ScanEventHandler(ifaceName));
	        if (!success) {
	            Log.e(TAG, "Failed to start single scan on " + ifaceName);
	            continue;
	        }
	        mStatusPerImpl.put(ifaceName, STATUS_PENDING);
	        anySuccess = true;
	    }
	    return anySuccess;
	}

这个方法中其实就是看系统有几种WifiScannerImpl类的实现方式,分别调用他们的startSingleScan方法。在安卓11源码中,有一个子类WificondScannerImpl继承了WifiScannerImpl,再分析WificondScannerImpl.startSingleScan.

	public boolean startSingleScan(WifiNative.ScanSettings settings,
	        WifiNative.ScanEventHandler eventHandler) {
	    /*省略部分代码*/
	    synchronized (mSettingsLock) {
	       /*省略部分代码*/
	        if (!allFreqs.isEmpty()) {
	            freqs = allFreqs.getScanFreqs();
	            success = mWifiNative.scan(
	                    getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet);
	            if (!success) {
	                Log.e(TAG, "Failed to start scan, freqs=" + freqs);
	            }
	        } else {
	            /*省略部分代码*/
	        }
	        /*省略部分代码*/
	        return true;
	    }
	}

前面一堆代码都是添加扫描的频段和隐藏的热点,重要的是后面调用的WifiNative.scan方法,这个方法才是往下走的。

	public boolean scan(
	        @NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, Set<Integer> freqs,
	        List<String> hiddenNetworkSSIDs) {
	    List<byte[]> hiddenNetworkSsidsArrays = new ArrayList<>();
	    for (String hiddenNetworkSsid : hiddenNetworkSSIDs) {
	        try {
	            hiddenNetworkSsidsArrays.add(
	                    NativeUtil.byteArrayFromArrayList(
	                            NativeUtil.decodeSsid(hiddenNetworkSsid)));
	        } catch (IllegalArgumentException e) {
	            Log.e(TAG, "Illegal argument " + hiddenNetworkSsid, e);
	            continue;
	        }
	    }
	    return mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays);
	}

在对隐藏热点再次处理后,调用了WifiNl80211Manager.startScan,接着看WifiNl80211Manager.

	public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
	        @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
	    IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
	    /*省略部分代码*/
	    SingleScanSettings settings = new SingleScanSettings();
	    try {
	        settings.scanType = getScanType(scanType);
	    } catch (IllegalArgumentException e) {
	        Log.e(TAG, "Invalid scan type ", e);
	        return false;
	    }
	    settings.channelSettings  = new ArrayList<>();
	    settings.hiddenNetworks  = new ArrayList<>();
	    if (freqs != null) {
	        for (Integer freq : freqs) {
	            ChannelSettings channel = new ChannelSettings();
	            channel.frequency = freq;
	            settings.channelSettings.add(channel);
	        }
	    }
	    if (hiddenNetworkSSIDs != null) {
	        for (byte[] ssid : hiddenNetworkSSIDs) {
	            HiddenNetwork network = new HiddenNetwork();
	            network.ssid = ssid;
	            // settings.hiddenNetworks is expected to be very small, so this shouldn't cause
	            // any performance issues.
	            if (!settings.hiddenNetworks.contains(network)) {
	                settings.hiddenNetworks.add(network);
	            }
	        }
	    }
	    try {
	        return scannerImpl.scan(settings);
	    } catch (RemoteException e1) {
	        Log.e(TAG, "Failed to request scan due to remote exception");
	    }
	    return false;
	}

方法前面部分确定了扫描类型、通道的扫描频率和隐藏热点,然后调用了IWifiScannerImpl接口的scan方法。IWifiScannerImpl接口为一个AIDL,其实现在下面的C++代码中。到这里整个扫描流程都分析完了,下面要继续分析是如何上报扫描结果。

【安卓Framework学习】Wifi框架学习之开启与关闭流程中分析到,在开启wifi时,会调用WifiNative中的setupInterfaceForClientInScanMode()方法,这个方法再来分析一次。

	public String setupInterfaceForClientInScanMode(
	        @NonNull InterfaceCallback interfaceCallback) {
	    synchronized (mLock) {
	        /*省略部分代码*/
	        if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run,
	                new NormalScanEventCallback(iface.name),
	                new PnoScanEventCallback(iface.name))) {
	            Log.e(TAG, "Failed to setup iface in wificond=" + iface.name);
	            teardownInterface(iface.name);
	            mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond();
	            return null;
	        }
	       /*省略部分代码*/
	        return iface.name;
	    }
	}

WifiNative方法中,向WifiNl80211Manager注册了NormalScanEventCallback回调类,再看WifiNl80211Manager.setupInterfaceForClientMode方法。

	public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
	        @NonNull @CallbackExecutor Executor executor,
	        @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) 
	   /*省略部分代码*/
	    try {
	        IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
	        if (wificondScanner == null) {
	            Log.e(TAG, "Failed to get WificondScannerImpl");
	            return false;
	        }
	        mWificondScanners.put(ifaceName, wificondScanner);
	        Binder.allowBlocking(wificondScanner.asBinder());
	        ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);
	        mScanEventHandlers.put(ifaceName,  scanEventHandler);
	        wificondScanner.subscribeScanEvents(scanEventHandler);
	        /*省略部分代码*/
	    } catch (RemoteException e) {
	        Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
	    }
	    return true;
	}

可以看到,调用了IWifiScannerImpl接口的实现类中注册了回调函数,之前有提到过此接口的实现在下面C++代码中,这里不往下分析。在看注册的回调类NormalScanEventCallback.

	private class NormalScanEventCallback implements WifiNl80211Manager.ScanEventCallback {
	    private String mIfaceName;
	    NormalScanEventCallback(String ifaceName) {
	        mIfaceName = ifaceName;
	    }
	    @Override
	    public void onScanResultReady() {
	        Log.d(TAG, "Scan result ready event");
	        mWifiMonitor.broadcastScanResultEvent(mIfaceName);
	    }
	    @Override
	    public void onScanFailed() {
	        Log.d(TAG, "Scan failed event");
	        mWifiMonitor.broadcastScanFailedEvent(mIfaceName);
	    }
	}

那么在扫描成功有扫描结果后,变回调用onScanResultReady回调方法,方法中通过WifiMonitor.broadcastScanResultEvent对外发送了监听消息,监听对应消息的对象都能收到此消息。再来看WifiMonitor.broadcastScanResultEvent方法。

	public void broadcastScanResultEvent(String iface) {
	    sendMessage(iface, SCAN_RESULTS_EVENT);
	}

这时,需要知道哪些类向WifiMonitor注册监听了SCAN_RESULTS_EVENT消息,回到WificondScannerImpl也就是WifiScannerImpl的子类,其构造方法如下。

	public WificondScannerImpl(Context context, String ifaceName, WifiNative wifiNative,
	                           WifiMonitor wifiMonitor, ChannelHelper channelHelper,
	                           Looper looper, Clock clock) {
	    /*省略部分代码*/
	    mEventHandler = new Handler(looper, this);
	    mClock = clock;
	    wifiMonitor.registerHandler(getIfaceName(),
	            WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
	    wifiMonitor.registerHandler(getIfaceName(),
	            WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
	    wifiMonitor.registerHandler(getIfaceName(),
	            WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
	}

可以看到是WificondScannerImpl注册监听了此消息,那么在看其是如何处理SCAN_RESULTS_EVENT消息的。

	public boolean handleMessage(Message msg) {
	    switch(msg.what) {
	        /*省略部分代码*/
	        case WifiMonitor.SCAN_RESULTS_EVENT:
	            cancelScanTimeout();
	            pollLatestScanData();
	            break;
	        default:
	            // ignore unknown event
	    }
	    return true;
	}

首先调用了cancelScanTimeout取消了在AlarmManager中注册的定时任务,然后进入pollLatestScanData方法中。

	private void pollLatestScanData() {
	    synchronized (mSettingsLock) {
	         /*省略部分代码*/
	         /*到这里其实扫描已经完成,然后扫描结果还存在hal层,这部分省略的代码其实就是从hal层取扫描结果然后处理成java层需要的样子*/
	        if (mLastScanSettings.singleScanEventHandler != null) {
	            if (mLastScanSettings.reportSingleScanFullResults) {
	                for (ScanResult scanResult : singleScanResults) {
	                    // ignore buckets scanned since there is only one bucket for a single scan
	                    mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
	                            /* bucketsScanned */ 0);
	                }
	            }
	            Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
	            mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
	                    getBandScanned(mLastScanSettings.singleScanFreqs),
	                    singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
	            mLastScanSettings.singleScanEventHandler
	                    .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
	        }
	        mLastScanSettings = null;
	    }
	}

这个方法主要是从HAL层拉去新扫描的数据,然后处理后调用回调通知上层。在前面调用WificondScannerImpl中的startSingleScan方法时,将一个LastScanSettings对象赋值给了mLastScanSettings,在mLastScanSettings内部有一个WifiNative.ScanEventHandler类的回调对象,其实现类为WifiScanningServiceImpl中的ScanEventHandler。这里在处理完扫描结果后,调用了WifiNative.ScanEventHandler类的回调对象的onScanStatus方法,所以这里我们直接看WifiScanningServiceImpl中的ScanEventHandler.onScanStatus.

	public void onScanStatus(int event) {
	    switch (event) {
	        case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE:
	        case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS:
	        case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT:
	            reportScanStatusForImpl(mImplIfaceName, STATUS_SUCCEEDED);
	            break;
	        case WifiNative.WIFI_SCAN_FAILED:
	            reportScanStatusForImpl(mImplIfaceName, STATUS_FAILED);
	            break;
	        default:
	            Log.e(TAG, "Unknown scan status event: " + event);
	            break;
	    }
	}

扫描成功的话在pollLatestScanData会发送WIFI_SCAN_RESULTS_AVAILABLE消息,然后调用reportScanStatusForImpl.

	private void reportScanStatusForImpl(@NonNull String implIfaceName, int newStatus) {
	    Integer currentStatus = mStatusPerImpl.get(implIfaceName);
	    if (currentStatus != null && currentStatus == STATUS_PENDING) {
	        mStatusPerImpl.put(implIfaceName, newStatus);
	    }
	    // Now check if all the scanner impls scan status is available.
	    int consolidatedStatus = getConsolidatedStatus();
	    if (consolidatedStatus == STATUS_SUCCEEDED) {
	        sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
	    } else if (consolidatedStatus == STATUS_FAILED) {
	        sendMessage(CMD_SCAN_FAILED);
	    }
	}

这里其实做的操作是检验是否所有的wifi芯片开启的接口扫描都是成功了,这里就先认为是都成功了,其实也会都成功,那么会继续给状态机发送CMD_SCAN_RESULTS_AVAILABLE消息,这时候根据前面的分析内容,状态机正处于ScanningState状态,那么转入ScanningState状态的处理方法。

	public boolean processMessage(Message msg) {
	    switch (msg.what) {
	        case CMD_SCAN_RESULTS_AVAILABLE:
	            ScanData latestScanResults =
	                    mScannerImplsTracker.getLatestSingleScanResults();
	            if (latestScanResults != null) {
	                /*省略部分代码*/
	                reportScanResults(latestScanResults);
	                mActiveScans.clear();
	            } else {
	                Log.e(TAG, "latest scan results null unexpectedly");
	            }
	            transitionTo(mIdleState);
	            return HANDLED;
	        /*省略部分代码*/
	    }
	}

主要看reportScanResults方法中做了什么操作。

	void reportScanResults(@NonNull ScanData results) {
	    /*省略部分代码*/
	    for (RequestInfo<Void> entry : mSingleScanListeners) {
	        logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
	                describeForLog(allResults));
	        entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
	    }
	    /*省略部分代码*/
	}

这里主要关注mSingleScanListeners变量中存的回调对象,会通过异步通道向WifiScanner发送CMD_SCAN_RESULT消息。那我们需要关注在什么时候向mSingleScanListeners变量中注册了回调对象。可以回看前面的分析,在开启扫描的时候调用了ScanRequestProxy.startScan,在此方法中第一行就调用了retrieveWifiScannerIfNecessary,转入分析ScanRequestProxy.retrieveWifiScannerIfNecessary

	private boolean retrieveWifiScannerIfNecessary() {
	    if (mWifiScanner == null) {
	        /*省略部分代码*/
	        if (mWifiScanner != null) {
	            mWifiScanner.registerScanListener(
	                    new HandlerExecutor(mHandler), new GlobalScanListener());
	        }
	    }
	    return mWifiScanner != null;
	}

可以看到将一个监听类注册到了WifiScanner中,并且记住这里的回调类是GlobalScanListener。在看WifiScanner.registerScanListener.

	public void registerScanListener(@NonNull @CallbackExecutor Executor executor,
	        @NonNull ScanListener listener) {
	    int key = addListener(listener, executor);
	    if (key == INVALID_KEY) return;
	    validateChannel();
	    mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
	}

WifiScanner通过异步通道向WifiScanningServiceImpl发送了CMD_REGISTER_SCAN_LISTENER消息,再看WifiScanningServiceImpl是如何处理CMD_REGISTER_SCAN_LISTENER消息。在WifiScanningServiceImpl中的ClientHandler.handleMessage方法中。

	public void handleMessage(Message msg) {
	 	/*省略部分代码*/
		switch (msg.what) {
		 /*省略部分代码*/
			case WifiScanner.CMD_REGISTER_SCAN_LISTENER:
			    logScanRequest("registerScanListener", ci, msg.arg2, null, null, null);
			    mSingleScanListeners.addRequest(ci, msg.arg2, null, null);
			    replySucceeded(msg);
			    break;
		}
		 /*省略部分代码*/
	}

这时候可以看到,向mSingleScanListeners添加了对应的回调信息,再次回到前面分析的reportScanResults方法,遍历了所有mSingleScanListeners中的回调,并发送CMD_SCAN_RESULT消息给WifiScanner。再看WifiScanner是如何处理CMD_SCAN_RESULT消息的。

	public void handleMessage(Message msg) {
	 	/*省略部分代码*/
		switch (msg.what) {
		 /*省略部分代码*/
			case CMD_SCAN_RESULT: {
			    ScanListener scanListener = (ScanListener) listener;
			    ParcelableScanData parcelableScanData = (ParcelableScanData) msg.obj;
			    Binder.clearCallingIdentity();
			    executor.execute(() -> scanListener.onResults(parcelableScanData.getResults()));
			} break;
		}
		 /*省略部分代码*/
	}

根据WifiScanningServiceImpl返回过来的回调信息,找出对应的回调对象listener,然后调用其onResults方法。由于在前面注册的回调类是GlobalScanListener,所以直接看GlobalScanListener.onResults.

	public void onResults(WifiScanner.ScanData[] scanDatas) {
	    /*省略部分代码*/
	    if (WifiScanner.isFullBandScan(scanData.getBandScanned(), false)) {
	        // Store the last scan results & send out the scan completion broadcast.
	        mLastScanResults.clear();
	        mLastScanResults.addAll(Arrays.asList(scanResults));
	        sendScanResultBroadcast(true);
	        sendScanResultsAvailableToCallbacks();
	    }
	}

在判断为全频段扫描后,直接调用sendScanResultBroadcast对外发送广播。

	private void sendScanResultBroadcast(boolean scanSucceeded) {
	    Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
	    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
	    intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded);
	    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
	}

当传入的参数scanSucceededfalse时,则表示没有新的扫描结果,所以一般获取EXTRA_RESULTS_UPDATED值为true时才去拿扫描结果数据。到这里整个扫描开始到有新的扫描结果然后对上发送广播的过程全部分析完了。

2、wifi扫描流程图

在这里插入图片描述

二、wifi扫描中的扫描模式

1、单次扫描

上半部分的分析内容其实就是单次扫描的整个流程,这里不做赘述了。

2、周期扫描

周期扫描其实是基于单次扫描的基础上,在java层Framework中设定一定的时间间隔,在屏幕亮起的情况下做的周期性单次扫描。原则上还是单次扫描,只是系统设定了定时任务,所以形成了周期扫描。周期扫描在wifi功能打开的时候会触发。在【安卓Framework学习】Wifi框架学习之开启与关闭流程中在开启wifi模块的过程中,ClientModeManager.ClientModeStateMachine会将状态机切换到ConnectModeState,看ConnectModeState.enter方法。

	public void enter() {
	    Log.d(TAG, "entering ConnectModeState");
	    mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE, mClientInterfaceName);
	    /*省略部分代码*/
	}

调用了ClientModeImpl.setOperationalMode,再看这个方法。

	public void setOperationalMode(int mode, String ifaceName) {
	    /*省略部分代码*/
	    if (mode != CONNECT_MODE) {
	        /*省略部分代码*/
	    } else {
	        // do a quick sanity check on the iface name, make sure it isn't null
	        if (ifaceName != null) {
	            mInterfaceName = ifaceName;
	            updateInterfaceCapabilities(ifaceName);
	            transitionTo(mDisconnectedState);
	            mWifiScoreReport.setInterfaceName(ifaceName);
	        } /*省略部分代码*/
	    }
	    sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE);
	}

由于传入的是CONNECT_MODE,所以会走下面的分支,传入的ifaceName,在这个状态之前底层就已经分配好了,所以不会为空。那么在这个分支里面会将ClientModeImpl的状态切换到DisconnectedState,但由于这个方法不是在ClientModeImpl的状态机的Handler中处理的,所以调用了sendMessageAtFrontOfQueue触发一次处理消息的机制,这样在这个方法后会将状态真实切换到DisconnectedState,进入DisconnectedState.enter.

	public void enter() {
	    /*省略部分代码*/
	    mWifiConnectivityManager.handleConnectionStateChanged(WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
	}

这里可以看到用到了WifiConnectivityManager,这个类在【安卓Framework学习】Wifi框架学习之核心类中有介绍到过,主要是管理wifi的扫描功能,那么进入这个方法。

	public void handleConnectionStateChanged(int state) {
	    /*省略部分代码*/
	    if (mDisconnectedSingleScanScheduleSec == null) {
	        mDisconnectedSingleScanScheduleSec =
	                initializeScanningSchedule(WIFI_STATE_DISCONNECTED);
	    }
	    /*省略部分代码*/
	    mWifiState = state;
	    if (mWifiState == WIFI_STATE_DISCONNECTED) {
	        mLastConnectionAttemptBssid = null;
	        scheduleWatchdogTimer();
	        setSingleScanningSchedule(mDisconnectedSingleScanScheduleSec);
	        startConnectivityScan(SCAN_IMMEDIATELY);
	    } /*省略部分代码*/
	}

mDisconnectedSingleScanScheduleSec这个就是一个存着一组数字,这组数字代表着间隔多少秒触发一次扫描,实现周期性扫描,具体这组数据是写在配置文件中,不做详细分析。主要看startConnectivityScan.

	private void startConnectivityScan(boolean scanImmediately) {
	    /*省略部分代码*/
	    stopConnectivityScan();
	    /*省略部分代码*/
	    if (mScreenOn) {
	        startPeriodicScan(scanImmediately);
	    } else {
	        if (mWifiState == WIFI_STATE_DISCONNECTED && !mPnoScanStarted) {
	            startDisconnectedPnoScan();
	        }
	    }
	}

方法中,首先停止了其他的所有扫描活动,然后判断当前屏幕是否亮起,这里我们认为屏幕是亮起的,因为屏幕关闭的情况下会走PNO扫描。进入startPeriodicScan方法中。

	private void startPeriodicScan(boolean scanImmediately) {
	    /*省略部分代码*/
	    if (scanImmediately) {
	        resetLastPeriodicSingleScanTimeStamp();
	    }
	    mCurrentSingleScanScheduleIndex = 0;
	    startPeriodicSingleScan();
	}

首先会重置一下周期扫描的时间戳,然后进入周期性扫描的方法中,mCurrentSingleScanScheduleIndex变量就是用来存前面得到的数组的索引值,表示当前的周期到了哪个值。再看startPeriodicSingleScan 方法。

	private void startPeriodicSingleScan() {
	    /*省略部分代码*/
	    if (isScanNeeded) {
	        mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
	        if (mWifiState == WIFI_STATE_DISCONNECTED
	                && mInitialScanState == INITIAL_SCAN_STATE_START) {
	            startSingleScan(false, WIFI_WORK_SOURCE);
	            if (mInitialScanState == INITIAL_SCAN_STATE_START) {
	                setInitialScanState(INITIAL_SCAN_STATE_AWAITING_RESPONSE);
	                mWifiMetrics.incrementInitialPartialScanCount();
	            }
	            return;
	        }
	        startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
	        schedulePeriodicScanTimer(
	                getScheduledSingleScanIntervalMs(mCurrentSingleScanScheduleIndex));
	        mCurrentSingleScanScheduleIndex++;
	    } else {
	        /*省略部分代码*/
	    }
	}

这里startSingleScan方法不做详细分析,因为随后马上调用到了WifiScanner.startScan方法中了接上了单次扫描的流程,所以不做分析了。主要看schedulePeriodicScanTimer方法是如何实现周期性的触发单次扫描。

	private void schedulePeriodicScanTimer(int intervalMs) {
	    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
	                        mClock.getElapsedSinceBootMillis() + intervalMs,
	                        PERIODIC_SCAN_TIMER_TAG,
	                        mPeriodicScanTimerListener, mEventHandler);
	    mPeriodicScanTimerSet = true;
	}

getScheduledSingleScanIntervalMs这个方法其实就是从之前的数组中取出当前所需要的那个时间,在这个方法中通过AlarmManager向系统中注册了一个定时任务在传入参数的毫秒数之后执行mPeriodicScanTimerListener,再看看mPeriodicScanTimerListener的实现。

private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener =
        new AlarmManager.OnAlarmListener() {
            public void onAlarm() {
                periodicScanTimerHandler();
            }
        };

当时间到时,调用periodicScanTimerHandler方法,再看periodicScanTimerHandler方法。

	private void periodicScanTimerHandler() {
	    localLog("periodicScanTimerHandler");
	    // Schedule the next timer and start a single scan if screen is on.
	    if (mScreenOn) {
	        startPeriodicSingleScan();
	    }
	}

这里判断了当屏幕亮的时候又调用了startPeriodicSingleScan方法,到这里又回到了之前的流程循环了,实现了周期性扫描。

3、PNO扫描

PNO扫描其实是需要软硬件结合的,是安卓为了功耗问题提供的一种更节能的后台扫描模式。主要是在没有连接wifi的情况下,并且屏幕是息屏状态,然后只去扫描已保存的热点配置。这样保证了设备在不开屏使用的情况下有最好的节能效果。PNO扫描的流程相对简单了,同样在startConnectivityScan方法中触发,如果这时候屏幕是处于熄灭状态并且wifi没有连接,就会调用进入startDisconnectedPnoScan方法。

	private void startDisconnectedPnoScan() {
	    // Initialize PNO settings
	    PnoSettings pnoSettings = new PnoSettings();
	    /*省略部分代码*/
	    mScanner.startDisconnectedPnoScan(
	            scanSettings, pnoSettings, new HandlerExecutor(mEventHandler), mPnoScanListener);
	    mPnoScanStarted = true;
	    mWifiMetrics.logPnoScanStart();
	}

调用了WifiScanner.startDisconnectedPnoScan.

	public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
	        @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
	    /*省略部分代码*/
	    pnoSettings.isConnected = false;
	    startPnoScan(scanSettings, pnoSettings, key);
	}
	
	private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
	    // Bundle up both the settings and send it across.
	    Bundle pnoParams = new Bundle();
	    // Set the PNO scan flag.
	    scanSettings.isPnoScan = true;
	    pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
	    pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
	    mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
	}

最后给WifiScanningServiceImpl发送了CMD_START_PNO_SCAN消息,这里WifiScanningServiceImpl直接将消息转发给了WifiPnoScanStateMachine状态机处理,如果硬件支持PNO扫描,则会切换到HwPnoScanState进行处理。再看HwPnoScanState.processMessage方法。

	public boolean processMessage(Message msg) {
	    ClientInfo ci = mClients.get(msg.replyTo);
	    switch (msg.what) {
	        case WifiScanner.CMD_START_PNO_SCAN:
	            /*省略部分代码*/
	            PnoSettings pnoSettings = null;
	            ScanSettings scanSettings = null;
	            try {
	                pnoSettings =
	                        pnoParams.getParcelable(
	                                WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
	                scanSettings =
	                        pnoParams.getParcelable(
	                                WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY);
	            } catch (BadParcelableException e) {
	                Log.e(TAG, "Failed to get parcelable params", e);
	                replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST,
	                        "bad parcel params");
	                return HANDLED;
	            }
	            if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
	                replySucceeded(msg);
	            } else {
	                replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
	                transitionTo(mStartedState);
	            }
	            break;
	        /*省略部分代码*/
	    }
	    return HANDLED;
	}

其中调用到了addHwPnoScanRequest方法。

	private boolean addHwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
	        PnoSettings pnoSettings) {
	     /*省略部分代码*/
	    WifiNative.PnoSettings nativePnoSettings =
	            convertSettingsToPnoNative(scanSettings, pnoSettings);
	    if (!mScannerImplsTracker.setHwPnoList(nativePnoSettings)) {
	        return false;
	    }
	     /*省略部分代码*/
	    return true;
	}

随后调用了ScannerImplsTracker.setHwPnoList.

	public boolean setHwPnoList(WifiNative.PnoSettings pnoSettings) {
	    mStatusPerImpl.clear();
	    boolean anySuccess = false;
	    for (Map.Entry<String, WifiScannerImpl> entry : mScannerImpls.entrySet()) {
	        String ifaceName = entry.getKey();
	        WifiScannerImpl impl = entry.getValue();
	        boolean success = impl.setHwPnoList(
	                pnoSettings, new PnoEventHandler(ifaceName));
	        if (!success) {
	            Log.e(TAG, "Failed to start pno on " + ifaceName);
	            continue;
	        }
	        mStatusPerImpl.put(ifaceName, STATUS_PENDING);
	        anySuccess = true;
	    }
	    return anySuccess;
	}

这里又调用到了WifiScannerImpl的实现类中,再看实现类WificondScannerImpl.setHwPnoList.

	public boolean setHwPnoList(WifiNative.PnoSettings settings,
	        WifiNative.PnoEventHandler eventHandler) {
	    synchronized (mSettingsLock) {
	        /*省略部分代码*/
	        mLastPnoScanSettings = new LastPnoScanSettings(
	                    mClock.getElapsedSinceBootMillis(),
	                    settings.networkList, eventHandler);
	        if (!startHwPnoScan(settings)) {
	            Log.e(TAG, "Failed to start PNO scan");
	            reportPnoScanFailure();
	        }
	        return true;
	    }
	}

	private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
	    return mWifiNative.startPnoScan(getIfaceName(), pnoSettings);
	}

调用了startHwPnoScan方法,这个方法直接调用到了WifiNative中,就不再继续往下分析了,后续和单次扫描大同小异,主要的还是得硬件能够支持这种省电的扫描模式。到这里PNO扫描就分析完了。


总结

安卓wifi框架中扫描功能与wifi开启关闭和连接断开有着较紧密的联系,并且wifi扫描本身也存在着较多的扫描模式,所以交织起来就相对较为复杂。相比起来连接和断开等其他的功能流程相对清晰单一,因此wifi的扫描功能还是需要结合场景来分析,这样思路就会清晰许多。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习Android Framework需要掌握以下几个方面: 1. Java基础知识:Android Framework是基于Java语言开发的,因此需要掌握Java的基础语法、面向对象编程思想、集合框架等知识点。 2. Android系统框架架构:要了解Android Framework的运行机制,需要学习Android系统的整体架构,包括Android系统的四大组件、应用程序框架、系统服务、HAL等。 3. Android Framework源码阅读:通过阅读Android Framework的源代码,可以深入了解Android系统的内部实现机制、原理和设计思路,从而更好地掌握Android Framework的开发。 4. 实践项目开发:通过实践项目开发,掌握Android Framework的具体应用场景和使用方法,了解Android Framework在实际开发中的应用。 具体学习思路可以分为以下几个步骤: 1. 学习Java基础知识,掌握Java语法、面向对象编程思想和集合框架。 2. 学习Android系统框架架构,了解Android系统的四大组件、应用程序框架、系统服务、HAL等。 3. 阅读Android Framework的源代码,了解Android Framework的内部实现机制、原理和设计思路。 4. 实践项目开发,掌握Android Framework的具体应用场景和使用方法。 5. 深入研究特定领域的Android Framework模块,如网络通信、图形处理、数据库等,掌握相关技术和实践经验。 6. 参与Android社区,了解最新的技术发展趋势和开发经验,与其他开发者交流分享,不断提升自己的技术水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值