Android11 Wifi开启、扫描和连接

2023-09-15 18:44:02

开启Wifi

开启Wifi开关,Wifi开关是WifiEnablerWifiEnabler实现了 SwitchWidgetController.OnSwitchChangeListener监听,打开/关闭开关会回调至

// 处理Switch 控件的状态变化事件   
 public boolean onSwitchToggled(boolean isChecked) {
        //Do nothing if called as a result of a state machine event
		// 通过Switch.setEnabled 方法设置Switch 控件状态时,不需要再次禁止/允许Wi-Fi,否则将造成循环调用的后果
        if (mStateMachineEvent) {
            return true;
        }
        // Show toast message if Wi-Fi is not allowed in airplane mode
		//如果在飞行模式时不允许单独使用Wi-Fi,使用Toast 信息框进行提示
        if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {
            Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
            // Reset switch to off. No infinite check/listener loop.
            mSwitchWidget.setChecked(false);
            return false;
        }
		
        if (isChecked) {
            mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_WIFI_ON);
        } else {
            // Log if user was connected at the time of switching off.
            mMetricsFeatureProvider.action(
                mContext, SettingsEnums.ACTION_WIFI_OFF,
                mConnected.get()
            );
        }
		 //通过WifiManager.setWifiEnabled()开启/关闭wifi
        if (!mWifiManager.setWifiEnabled(isChecked)) {
            // Error
            mSwitchWidget.setEnabled(true);
            Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
        }
        return true;
    }

mWifiManager.setWifiEnabled(isChecked) 用于根据Switch控件的当前状态关闭或打开Wi-Fi。

onCheckedChanged方法的开始部分使用了一个mStateMachineEvent变量,当该变量为true时直接跳出了onCheckedChanged方法。实际上,增加这个跳出条件的原因是因为Switch控件的状态变化可以有如下两种情况。

  • 直接单击Switch 控件。

  • 调用Switch.setChecked 方法。

遗憾的是,上述两种情况都会触发onCheckedChanged方法的调用。不管是哪种方法使Switch控件的状态发生了变化,在onCheckedChanged中调用WifiManager.setWifiEnabled方法设置Wi-Fi状态都会再次触发 onCheckedChanged方法的调用。当再次调用onCheckedChanged 方法时就需要将mStateMachineEvent变量值设为true,这样onCheckedChanged方法在执行之初就会立刻返回(如果继续执行后面的代码,将会造成死循环),然后再将mStateMachineEvent变量值设为false。这样在下一次设置Wi-Fi状态时仍然可以继续执行onCheckedChanged方法了。

用广播方式设置 Switch 控件的状态

 /packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java
private 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)) {
            handleWifiStateChanged(mWifiManager.getWifiState());
        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
            if (!mConnected.get()) {
                handleStateChanged(
                    WifiInfo.getDetailedStateOf(
                        (SupplicantState)
                                intent . getParcelableExtra (WifiManager.EXTRA_NEW_STATE)
                    )
                );
            }
        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            NetworkInfo info =(NetworkInfo) intent . getParcelableExtra (
                    WifiManager.EXTRA_NETWORK_INFO);
            mConnected.set(info.isConnected());
            handleStateChanged(info.getDetailedState());
        }
    }
};

对于Wi-Fi来说,这个广播接收器主要用于接收Wi-Fi的状态变化,并做进一步地处理。其中处理Wi-Fi状态变化的方法是handleWifiStateChanged

private void handleWifiStateChanged(int state) {
    // Clear any previous state
    mSwitchWidget.setDisabledByAdmin(null);

    switch(state) {
        case WifiManager . WIFI_STATE_ENABLING : // 处理正在开启Wi-Fi的状态
        break;
        case WifiManager . WIFI_STATE_ENABLED : // 处理已经开启Wi-Fi的状态
        setSwitchBarChecked(true);
        mSwitchWidget.setEnabled(true);
        break;
        case WifiManager . WIFI_STATE_DISABLING : // 处理正在关闭Wi-Fi的状态
        break;
        case WifiManager . WIFI_STATE_DISABLED :  // 处理已经关闭Wi-Fi的状态
        setSwitchBarChecked(false);
        mSwitchWidget.setEnabled(true);
        break;
        default:                                   // 处理其他情况
        setSwitchBarChecked(false);
        mSwitchWidget.setEnabled(true);
    }
}



 /packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java
// 如果当前Wi-Fi 的状态与Switch 控件的状态不一致,改变Switch 控件的状态
private void setSwitchBarChecked(boolean checked) {
    mStateMachineEvent = true;
    mSwitchWidget.setChecked(checked);
    mStateMachineEvent = false;
}

setSwitchChecked方法代码可以看出,在改变Switch控件状态之前(调用Switch.setChecked方法),先将mStateMachineEvent变量设为true。这就意味着在设置Switch控件状态时不会由于触发了onCheckedChanged方法而再次设置Wi-Fi状态,从而进行递归调用。

从这一点可以看出,WifiEnabler类中的广播接收器的目的只是为了设置Switch控件的状态,并不是为了设置Wi-Fi的状态。这么做的目的是当用户通过WifiManager.setWifiEnabled方法设置Wi-Fi状态时,尽管可以成功设置Wi-Fi的状态,但却无法改变Switch控件的状态,所以就会造成当前Wi-Fi状态与Switch控件的状态不一致的情况。为了解决这个问题,WifiManager.setWifiEnabled方法在设置Wi-Fi状态后,会发送一个广播(WifiManager.WIFI_STATE_CHANGED_ACTION),然后WifiEnabler类中的广播接收器就会接收到这个广播,并根据当前Wi-Fi的状态改变Switch控件的状态。

扫描WiFi

开始扫描的逻辑是从WifiSettings触发的。
WifiTracker接收到wifi状态改变的广播以后,

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)) { // 处理热点搜索完成的动作
                mStaleScanResults = false;
                mLastScanSucceeded =
                        intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
				// 更新搜索到的热点
                fetchScansAndConfigsAndUpdateAccessPoints();
            } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
                    || WifiManager.ACTION_LINK_CONFIGURATION_CHANGED.equals(action)) {
                fetchScansAndConfigsAndUpdateAccessPoints();
            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { // 网络状态变化动作
                // TODO(sghuman): Refactor these methods so they cannot result in duplicate
                // onAccessPointsChanged updates being called from this intent.
                NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                updateNetworkInfo(info);
                fetchScansAndConfigsAndUpdateAccessPoints();
            } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
                updateNetworkInfo(/* networkInfo= */ null);
            }
        }
    };


如果wifi已经打开了,则开始扫描Wifi

    private void updateWifiState(int state) {
        if (isVerboseLoggingEnabled()) {
            Log.d(TAG, "updateWifiState: " + state);
        }
        if (state == WifiManager.WIFI\_STATE\_ENABLED) {
            synchronized (mLock) {
                if (mScanner != null) {
// We only need to resume if mScanner isn't null because
// that means we want to be scanning.
                    mScanner.resume();
                }
            }
        }
        mListener.onWifiStateChanged(state);
    }

Scanner是用于扫描Wifi的类:

class Scanner extends Handler {
    static final int MSG_SCAN = 0;

    private int mRetry = 0;

    // 通过resume 方法可以触发循环扫描热点
    void resume () {
        if (isVerboseLoggingEnabled()) {
            Log.d(TAG, "Scanner resume");
        }
        if (!hasMessages(MSG_SCAN)) {
            sendEmptyMessage(MSG_SCAN);
        }
    }

    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;
        }
        // 发送延迟消息,会每10 秒搜索一次热点
        sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
    }
}

首先会由WifiSettings类中创建的广播接收器处理WifiManager.NETWORK_STATE_CHANGED_ACTION 广播动作,然后在处理的过程中调用了Scanner.resume 方法开始扫描热点。当热点扫描完成后,系统发送 WifiManager.SCAN_RESULTS_AVAILABLE_ACTION广播。

调用fetchScansAndConfigsAndUpdateAccessPoints方法更新热点列表,具体更新是在updateAccessPoints()。这里的Scanner对象通过不断在Scanner. handleMessage方法中发送延迟消息,从而使系统每隔一定时间(10秒)就会扫描一次热点。

private void fetchScansAndConfigsAndUpdateAccessPoints() {
		// 获取所有搜索到的热点信息
        List<ScanResult> newScanResults = mWifiManager.getScanResults();

        // Filter all unsupported networks from the scan result list
        final List<ScanResult> filteredScanResults =
                filterScanResultsByCapabilities(newScanResults);

//        if (isVerboseLoggingEnabled()) {
            Log.i(TAG, "Fetched scan results: " + filteredScanResults);
//        }
		// 获取当前已经连接的热点信息
        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
        updateAccessPoints(filteredScanResults, configs);
    }

updateAccessPoints(filteredScanResults, configs);中会创建已经连接的热点信息的AccessPointer对象,调用accessPoint.update更新状态信息,将热点信息添加到热点,最后回调WifiSettings中的onAccessPointsChanged()更新UI;

连接Wifi

当用户单击一个热点后,就会弹出对话框要求输入密码,最后单击“连接”按钮进行连接。具体是在WifiSettings中的submit方法中:

 void submit(WifiConfigController configController) {

    final WifiConfiguration config = configController.getConfig();

    if (config == null) {
        if (mSelectedAccessPoint != null
                && mSelectedAccessPoint.isSaved()) {
            connect(mSelectedAccessPoint.getConfig(),
                    true /* isSavedNetwork */,
                    CONNECT_SOURCE_UNSPECIFIED);
        }
    } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
        mWifiManager.save(config, mSaveListener);
    } else {
        mWifiManager.save(config, mSaveListener);
        if (mSelectedAccessPoint != null) { // Not an "Add network"
            connect(config, false /* isSavedNetwork */,
                    CONNECT_SOURCE_UNSPECIFIED);
        }
    }

    mWifiTracker.resumeScanning();
}



protected void connect(final WifiConfiguration config,
        boolean isSavedNetwork, @ConnectSource int connectSource) {
    // Log subtype if configuration is a saved network.
    mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_WIFI_CONNECT,
            isSavedNetwork);
    mConnectSource = connectSource;
	// 连接Wifi
    mWifiManager.connect(config, mConnectListener);
    mClickedConnect = true;
}

点击连接以后,如果config不为null,则先保存网络,再进行连接,所以即使连接失败,此网络依然在已保存网络列表里。

更多推荐

python浮点数比较的三种方法

在Python中,由于浮点数在计算机内部的表示方式是二进制的,因此进行浮点数比较时可能会出现精度问题。为了解决这个问题,你可以使用以下几种方法来比较浮点数:使用近似比较:通过设置一个小的误差范围,在比较两个浮点数时,判断它们的差是否在这个误差范围内。例如,你可以定义一个epsilon值(即允许的误差范围)来比较两个浮点

国产自研BI系统,更懂中国企业数据分析需求

国产自研BI系统是指由中国企业自主研发的商业智能(BI)系统,这类系统更加了解中国企业的数据分析需求,能够提供更加贴合实际的解决方案。比如说奥威BI系统就是典型的国产自研,不仅了解中国企业的数据分析需求,还根据多年的经验为中国企业量身打造了多套标准化的BI数据分析解决方案,为中国企业提供更低风险、高效性、高性价比的智能

分布式锁实现方法

分布式锁什么时候需要加锁有并发,多线程有写操作有竞争关系场景:电商系统,下单流程:用户下单–>秒杀系统检查redis商品库存信息–>用户锁定并更新库存(mysql)—>秒杀系统更新redis问题:单机部署,单线程执行无问题,多线程并发操作会引起超卖解决:对用户下单后的步骤加锁,让线程排队,避免超卖(synchroniz

Caddy Web服务器深度解析与对比:Caddy vs. Nginx vs. Apache

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

电脑桌面透明便签软件是哪个?

在现代快节奏的工作环境中,许多上班族都希望能够在电脑桌面上方便地记录工作资料、重要事项、工作流程等内容。为了解决这个问题,一款优秀的电脑桌面便签软件是必不可少的。在选择桌面便签软件时,许多用户也希望便签软件能够与电脑桌面壁纸相统一,提升桌面美观度。那么,桌面便签软件哪个好用?有没有一款支持透明皮肤的电脑桌面便签软件值得

Java对比对象修改前与修改后字段发生的变化

开发过程中,我们通常会对系统操作人对系统的操作进行记录,记录操作前后某个字段的变化,如下图2.提供一个工具类,可以比较一个对象里面,源对象,与修改后的对象,有哪些字段发生了改变,第一步/***@authorqiankun.hu*@version1.0.0*@createTime2023年09月20日17:00:00*@

“MicroPython在微控制器上实现I2S支持:详细指南与完整Python示例代码

第一部分:MicroPython与微控制器简介1.MicroPython简介MicroPython是Python3的精简、高效、快速实现,专为微控制器和受限制的环境设计。与传统的Python相比,MicroPython是为了在资源受限的设备上运行而优化的,这意味着它可以在只有几十KB的RAM和存储空间的设备上运行。2.

Python的多重继承和MixIn

前言:嗨喽~大家好呀,这里是魔王呐❤~!python更多源码/资料/解答/教程等点击此处跳转文末名片免费获取多重继承继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。回忆一下Animal类层次的设计,假设我们要实现以下4种动物:Dog-狗狗;Bat-蝙蝠;Parrot-鹦鹉;Ostrich-鸵

FFMpeg zoompan 镜头聚焦和移动走位

案例原始图片#输出帧数,默认25帧/秒,25*4代表4秒#s=1280x80#输出视频比例,可以设置和输入图片大小一致#zoom+0.002表示每帧放大的倍数,下面代码是25帧/每秒*4秒,共1000帧#最终是0.002*25*4=0.2,最终是放大1.2倍ffmpeg-i"./sdout/1.jpg"\-filter

APP应用如何选择云服务器?又如何备案呢?

APP备案应到其网络接入服务商处进行提交,如APP使用的是西部数码云服务器,就通过西部数码提交APP备案申请。APP备案申请前,需要开通一台云服务器。购买云服务器时,需要先了解APP应用的需求和估算负载量,以便选择到大小合适的云服务器。1.了解APP应用需求需要了解APP应用程序需要多少计算资源、存储空间和带宽等。这将

simulink代码生成

文章目录simulink代码生成simulink生成嵌入式C代码simulink生成AUTOSAR代码simulink代码生成Simulink是一种基于图形的编程环境,用于建模、仿真和分析动态系统。它是MathWorks公司的MATLAB软件的一个附加产品。Simulink代码生成是指使用Simulink模型生成可在其

热文推荐