Wifi开发上层学习1:实现一个wifi搜索以及打开的app

文章目录

  • Wifi开发上层学习1:实现一个wifi搜索以及打开的app
    • 背景
    • demo实现
      • 1.添加系统权限以及系统签名
      • 2.布局配置
      • 3.逻辑设计
        • 3.1 wifi开关的实现
        • 3.2 wifi扫描功能
        • 3.3 连接wifi
    • 总结
      • 一、WiFi 状态控制接口
      • 二、WiFi 扫描相关接口
      • 三、WiFi 连接与配置管理接口
      • 四、连接状态信息接口
      • 关键广播(配合接口使用)

背景

接下来会从上到下贯穿的学习wifi,相机等系统功能。今天是学习wifi的第一天,主要是调用wifiManager的一些api来实现wifi功能的开关,wifi搜索,以及记录和连接。

demo实现

1.添加系统权限以及系统签名

添加权限

<!-- WiFi状态获取 --><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><!-- WiFi状态修改 --><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /><!-- Android 10+ 扫描WiFi需位置权限 --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><!-- Android 12+ 扫描WiFi需附近WiFi权限(无需定位) --><uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />

添加系统签名

具体流程不展示,注意gradle中配置的密码等需要匹配,以及在manifest中配置android:sharedUserId="android.uid.system"

2.布局配置

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="16dp"tools:context=".MainActivity"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/app_name"android:textSize="24sp"android:textStyle="bold"android:layout_marginBottom="16dp" /><!-- WiFi开关 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"android:layout_marginBottom="16dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="WiFi开关:"android:textSize="18sp"/><Switchandroid:id="@+id/wifi_switch"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"/></LinearLayout><!-- 扫描按钮 --><Buttonandroid:id="@+id/scan_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="扫描WiFi网络"android:layout_marginBottom="16dp"/><!-- 扫描结果列表 --><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="可用网络:"android:textSize="18sp"android:layout_marginBottom="8dp"/><ListViewandroid:id="@+id/wifi_list"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

主界面布局非常简单一个switch,下面一个扫描按钮,最后的扫描结果使用listView展示

wifi_list_item.layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="16dp"android:gravity="center_vertical"><!-- WiFi名称 + 已连接标识 + 已保存标识 --><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="horizontal"android:gravity="center_vertical"><!-- WiFi名称 --><TextViewandroid:id="@+id/ssid_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="16sp"android:textColor="@android:color/black"/><!-- 已连接标签(必须有@+id/tv_connected) --><TextViewandroid:id="@+id/tv_connected"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="(已连接)"android:textSize="14sp"android:textColor="@android:color/holo_green_dark"android:layout_marginStart="8dp"android:visibility="gone"/><!-- 已保存标签(关键:补全@+id/saved_indicator) --><TextViewandroid:id="@+id/saved_indicator"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="(已保存)"android:textSize="14sp"android:textColor="@android:color/holo_blue_dark"android:layout_marginStart="8dp"android:visibility="gone"/></LinearLayout><!-- 信号强度 --><TextViewandroid:id="@+id/strength_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="14sp"android:textColor="@android:color/darker_gray"android:layout_marginStart="16dp"android:layout_marginEnd="16dp"/><!-- 加密方式 --><TextViewandroid:id="@+id/security_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="14sp"android:textColor="@android:color/darker_gray"/></LinearLayout>

item设计,主要设置两个默认不可见的标签,分别标识已连接的wifi以及已保存的wifi

dialog_connect.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="16dp"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="连接到网络"android:textSize="18sp"android:textStyle="bold"android:layout_marginBottom="16dp" /><TextViewandroid:id="@+id/dialog_ssid"android:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="16sp"android:layout_marginBottom="16dp" /><EditTextandroid:id="@+id/password_input"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="输入密码"android:inputType="textPassword"/></LinearLayout>

3.逻辑设计

 // 初始化WiFi管理器wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

首先需要拿到wifiManager的实例

3.1 wifi开关的实现

安卓12之后不允许应用修改wifi开关,所以这里必须要系统签名

核心方法

wifiManager.setWifiEnabled();

之后会更新这个逻辑链怎么传到下层

wifiSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {if (isChecked && !wifiManager.isWifiEnabled()) {checkPermissionsAndEnableWifi();} else if (!isChecked && wifiManager.isWifiEnabled()) {wifiManager.setWifiEnabled(false);}});

checkPermissionsAndEnableWifi()这其中与下面的关闭一样,就是多加个权限检查
更新开关状态

// 更新WiFi开关状态private void updateWifiSwitchState(int state) {Log.d(TAG, "updateWifiSwitchState: " + state);switch (state) {case WifiManager.WIFI_STATE_ENABLED:wifiSwitch.setEnabled(true);wifiSwitch.setChecked(true);scanButton.setEnabled(true);// 刷新当前连接的WiFi信息currentConnectedSSID = getCurrentConnectedWifiSSID();configuredNetworks = wifiManager.getConfiguredNetworks();break;case WifiManager.WIFI_STATE_DISABLED:wifiSwitch.setEnabled(true);wifiSwitch.setChecked(false);scanButton.setEnabled(false);sortedScanResults.clear();adapter.notifyDataSetChanged();break;default:wifiSwitch.setEnabled(false);scanButton.setEnabled(false);break;}}
3.2 wifi扫描功能

wifi搜索的过程可以分为

配置scanButton的点击事件

//onCreate
// 扫描按钮监听scanButton.setOnClickListener(v -> checkPermissionsAndScan());private void checkPermissionsAndScan() {if (hasRequiredPermissions()) {startWifiScan();} else {requestPermissions();}}
//开始扫描private void startWifiScan() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isForeground()) {Toast.makeText(this, "应用需要在前台才能扫描WiFi",Toast.LENGTH_SHORT).show();return;}boolean started = wifiManager.startScan();if (!started) {Toast.makeText(this, "扫描失败,请重试", Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "开始扫描...", Toast.LENGTH_SHORT).show();}}
// 检查应用是否在前台private boolean isForeground() {ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();if (appProcesses == null) {return false;}final String packageName = getPackageName();for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {if (appProcess.processName.equals(packageName) &&appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {return true;}}return false;}

核心方法 wifiManager.startScan()

3.3 连接wifi

1.列表项点击事件

 private void handleWifiItemClick(ScanResult result) {// 检查是否是已保存网络if (isWifiSaved(result.SSID)) {// 已保存网络:直接连接connectToSavedWifi(result.SSID);} else {// 未保存网络:显示密码对话框showConnectDialog(result);}}
 // 判断WiFi是否已保存private boolean isWifiSaved(String ssid) {if (configuredNetworks == null || configuredNetworks.isEmpty()) {return false;}// 已保存网络的SSID带双引号String targetSSID = "\"" + ssid + "\"";for (WifiConfiguration config : configuredNetworks) {if (targetSSID.equals(config.SSID)) {return true;}}return false;}

对比系统中已保存的 WiFi 配置列表(WifiManager.getConfiguredNetworks())与当前 WiFi 的 SSID,若存在匹配项,则视为 “已保存”。

 // 连接已保存的WiFi private void connectToSavedWifi(String ssid) {WifiConfiguration savedConfig = getSavedWifiConfig(ssid);if (savedConfig != null) {// 检查是否已是当前连接的网络if (ssid.equals(currentConnectedSSID)) {Toast.makeText(this, "已是当前连接的网络:" + ssid, Toast.LENGTH_SHORT).show();return;}// 断开当前连接,连接选中的已保存网络wifiManager.disconnect();boolean isConnected = wifiManager.enableNetwork(savedConfig.networkId, true);wifiManager.reconnect();if (isConnected) {Toast.makeText(this, "正在连接:" + ssid, Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "连接失败,请重试", Toast.LENGTH_SHORT).show();}} else {// 理论上不会走到这里,因为已做过isWifiSaved判断Toast.makeText(this, "网络配置不存在", Toast.LENGTH_SHORT).show();}}

wifiManager.disconnect(); boolean isConnected = wifiManager.enableNetwork(savedConfig.networkId, true); wifiManager.reconnect();//核心逻辑

//未保存的wifi,打开dialog 
private void showConnectDialog(ScanResult scanResult) {AlertDialog.Builder builder = new AlertDialog.Builder(this);LayoutInflater inflater = getLayoutInflater();View dialogView = inflater.inflate(R.layout.dialog_connect, null);builder.setView(dialogView);TextView ssidText = dialogView.findViewById(R.id.dialog_ssid);EditText passwordInput = dialogView.findViewById(R.id.password_input);ssidText.setText(scanResult.SSID);// 开放网络隐藏密码输入框if (!scanResult.capabilities.contains("WEP") && !scanResult.capabilities.contains("WPA")) {passwordInput.setVisibility(View.GONE);}builder.setPositiveButton("连接", (dialog, which) -> {String password = passwordInput.getText().toString();connect(scanResult.SSID, password, scanResult.capabilities);});builder.setNegativeButton("取消", null);builder.show();}

连接wifi的核心方法

// 连接WiFi的核心方法private void connect(String ssid, String password, String capabilities) {// 先检查是否已保存该WiFi(防止重复保存)WifiConfiguration savedConfig = getSavedWifiConfig(ssid);if (savedConfig != null) {connectToSavedWifi(ssid);return;}// 未保存:创建新配置并保存WifiConfiguration newConfig = new WifiConfiguration();newConfig.SSID = "\"" + ssid + "\"";// 根据加密类型设置配置if (capabilities.contains("WEP")) {newConfig.wepKeys[0] = "\"" + password + "\"";newConfig.wepTxKeyIndex = 0;newConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);newConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);} else if (capabilities.contains("WPA")) {newConfig.preSharedKey = "\"" + password + "\"";} else {// 开放网络newConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);}// 添加配置并连接int networkId = wifiManager.addNetwork(newConfig);if (networkId != -1) {// 保存配置wifiManager.saveConfiguration();// 启用该网络并断开其他网络wifiManager.disconnect();boolean isConnected = wifiManager.enableNetwork(networkId, true);wifiManager.reconnect();if (isConnected) {Toast.makeText(this, "正在连接:" + ssid, Toast.LENGTH_SHORT).show();// 更新已保存列表configuredNetworks = wifiManager.getConfiguredNetworks();} else {// 连接失败:删除无效配置wifiManager.removeNetwork(networkId);wifiManager.saveConfiguration();Toast.makeText(this, "连接失败,请检查密码", Toast.LENGTH_SHORT).show();}} else {Toast.makeText(this, "无法添加网络配置", Toast.LENGTH_SHORT).show();}}

总结

完整代码

package com.example.wifitest;import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class MainActivity extends AppCompatActivity {private static final int PERMISSION_REQUEST_CODE = 100;public static final String TAG = "WifiTest";private WifiManager wifiManager;private Switch wifiSwitch;private Button scanButton;private ListView wifiList;private WifiListAdapter adapter;// 当前连接的WiFi SSID(不带双引号)private String currentConnectedSSID;// 排序后的WiFi列表private List<ScanResult> sortedScanResults;// 已保存的WiFi配置列表private List<WifiConfiguration> configuredNetworks;// WiFi状态广播接收器private BroadcastReceiver wifiStateReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);updateWifiSwitchState(state);}};// WiFi扫描结果广播接收器(修改:确保排序正确)private BroadcastReceiver scanResultsReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);if (success && wifiManager.isWifiEnabled()) {List<ScanResult> tempResults = wifiManager.getScanResults();// 步骤1:去重(同一SSID保留信号最强的)List<ScanResult> uniqueResults = removeDuplicateSSID(tempResults);// 步骤2:按规则排序(当前连接置顶)sortWifiList(uniqueResults);// 步骤3:更新列表adapter.notifyDataSetChanged();Toast.makeText(MainActivity.this,"扫描完成,发现 " + sortedScanResults.size() + " 个网络",Toast.LENGTH_SHORT).show();}}};// 监听WiFi连接状态变化的广播接收器(新增)private BroadcastReceiver connectionStateReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 当WiFi连接状态变化时,更新当前连接信息并重新排序currentConnectedSSID = getCurrentConnectedWifiSSID();configuredNetworks = wifiManager.getConfiguredNetworks();if (sortedScanResults != null && !sortedScanResults.isEmpty()) {sortWifiList(sortedScanResults);adapter.notifyDataSetChanged();}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化WiFi管理器wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);// 初始化视图wifiSwitch = findViewById(R.id.wifi_switch);scanButton = findViewById(R.id.scan_button);wifiList = findViewById(R.id.wifi_list);// 初始化成员变量currentConnectedSSID = getCurrentConnectedWifiSSID();configuredNetworks = wifiManager.getConfiguredNetworks();sortedScanResults = new ArrayList<>();// 初始化适配器adapter = new WifiListAdapter();wifiList.setAdapter(adapter);// 注册广播接收器(新增连接状态变化接收器)registerReceiver(wifiStateReceiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));registerReceiver(scanResultsReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));registerReceiver(connectionStateReceiver, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));// WiFi开关监听wifiSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {if (isChecked && !wifiManager.isWifiEnabled()) {checkPermissionsAndEnableWifi();} else if (!isChecked && wifiManager.isWifiEnabled()) {wifiManager.setWifiEnabled(false);}});// 扫描按钮监听scanButton.setOnClickListener(v -> checkPermissionsAndScan());// 列表项点击事件(修改:区分已保存和未保存网络)wifiList.setOnItemClickListener((parent, view, position, id) -> {if (sortedScanResults != null && position < sortedScanResults.size()) {ScanResult result = sortedScanResults.get(position);handleWifiItemClick(result);}});// 初始状态更新updateWifiSwitchState(wifiManager.getWifiState());}@Overrideprotected void onDestroy() {super.onDestroy();unregisterReceiver(wifiStateReceiver);unregisterReceiver(scanResultsReceiver);unregisterReceiver(connectionStateReceiver); // 注销新增的接收器}// 更新WiFi开关状态private void updateWifiSwitchState(int state) {Log.d(TAG, "updateWifiSwitchState: " + state);switch (state) {case WifiManager.WIFI_STATE_ENABLED:wifiSwitch.setEnabled(true);wifiSwitch.setChecked(true);scanButton.setEnabled(true);// 刷新当前连接的WiFi信息currentConnectedSSID = getCurrentConnectedWifiSSID();configuredNetworks = wifiManager.getConfiguredNetworks();break;case WifiManager.WIFI_STATE_DISABLED:wifiSwitch.setEnabled(true);wifiSwitch.setChecked(false);scanButton.setEnabled(false);sortedScanResults.clear();adapter.notifyDataSetChanged();break;default:wifiSwitch.setEnabled(false);scanButton.setEnabled(false);break;}}// 处理WiFi列表项点击(新增:区分已保存和未保存网络)private void handleWifiItemClick(ScanResult result) {// 检查是否是已保存网络if (isWifiSaved(result.SSID)) {// 已保存网络:直接连接connectToSavedWifi(result.SSID);} else {// 未保存网络:显示密码对话框showConnectDialog(result);}}// 显示连接对话框(提取为独立方法)private void showConnectDialog(ScanResult scanResult) {AlertDialog.Builder builder = new AlertDialog.Builder(this);LayoutInflater inflater = getLayoutInflater();View dialogView = inflater.inflate(R.layout.dialog_connect, null);builder.setView(dialogView);TextView ssidText = dialogView.findViewById(R.id.dialog_ssid);EditText passwordInput = dialogView.findViewById(R.id.password_input);ssidText.setText(scanResult.SSID);// 开放网络隐藏密码输入框if (!scanResult.capabilities.contains("WEP") && !scanResult.capabilities.contains("WPA")) {passwordInput.setVisibility(View.GONE);}builder.setPositiveButton("连接", (dialog, which) -> {String password = passwordInput.getText().toString();connect(scanResult.SSID, password, scanResult.capabilities);});builder.setNegativeButton("取消", null);builder.show();}// 连接已保存的WiFi(新增)private void connectToSavedWifi(String ssid) {WifiConfiguration savedConfig = getSavedWifiConfig(ssid);if (savedConfig != null) {// 检查是否已是当前连接的网络if (ssid.equals(currentConnectedSSID)) {Toast.makeText(this, "已是当前连接的网络:" + ssid, Toast.LENGTH_SHORT).show();return;}// 断开当前连接,连接选中的已保存网络wifiManager.disconnect();boolean isConnected = wifiManager.enableNetwork(savedConfig.networkId, true);wifiManager.reconnect();if (isConnected) {Toast.makeText(this, "正在连接:" + ssid, Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "连接失败,请重试", Toast.LENGTH_SHORT).show();}} else {// 理论上不会走到这里,因为已做过isWifiSaved判断Toast.makeText(this, "网络配置不存在", Toast.LENGTH_SHORT).show();}}// 获取当前连接的WiFi SSIDprivate String getCurrentConnectedWifiSSID() {if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) {return null;}WifiInfo wifiInfo = wifiManager.getConnectionInfo();// 判断是否真的连接了WiFi(networkId != -1 表示已连接)if (wifiInfo != null && wifiInfo.getNetworkId() != -1) {String ssid = wifiInfo.getSSID();// 去掉SSID前后的双引号if (ssid.startsWith("\"") && ssid.endsWith("\"")) {ssid = ssid.substring(1, ssid.length() - 1);}return ssid;}return null;}// 去重:同一SSID只保留信号最强的private List<ScanResult> removeDuplicateSSID(List<ScanResult> results) {Map<String, ScanResult> uniqueMap = new HashMap<>();for (ScanResult result : results) {String ssid = result.SSID;// 跳过空SSIDif (ssid.isEmpty()) continue;// 若SSID未存在,或当前信号更强,则更新if (!uniqueMap.containsKey(ssid) || result.level > uniqueMap.get(ssid).level) {uniqueMap.put(ssid, result);}}return new ArrayList<>(uniqueMap.values());}// 按规则排序:当前连接>已保存>未保存,同组内信号强在前private void sortWifiList(List<ScanResult> results) {if (results == null || results.isEmpty()) {sortedScanResults = new ArrayList<>();return;}// 实时更新已保存网络列表和当前连接SSIDconfiguredNetworks = wifiManager.getConfiguredNetworks();currentConnectedSSID = getCurrentConnectedWifiSSID();Collections.sort(results, new Comparator<ScanResult>() {@Overridepublic int compare(ScanResult r1, ScanResult r2) {// 优先级1:当前连接的WiFi置顶boolean isConn1 = r1.SSID.equals(currentConnectedSSID);boolean isConn2 = r2.SSID.equals(currentConnectedSSID);if (isConn1 && !isConn2) return -1; // r1在前if (!isConn1 && isConn2) return 1;  // r2在前// 优先级2:已保存的WiFi排在未保存前面boolean isSaved1 = isWifiSaved(r1.SSID);boolean isSaved2 = isWifiSaved(r2.SSID);if (isSaved1 && !isSaved2) return -1;if (!isSaved1 && isSaved2) return 1;// 优先级3:信号强度降序(level越大信号越强)return Integer.compare(r2.level, r1.level);}});sortedScanResults = results;}// 判断WiFi是否已保存private boolean isWifiSaved(String ssid) {if (configuredNetworks == null || configuredNetworks.isEmpty()) {return false;}// 已保存网络的SSID带双引号String targetSSID = "\"" + ssid + "\"";for (WifiConfiguration config : configuredNetworks) {if (targetSSID.equals(config.SSID)) {return true;}}return false;}// 获取已保存的WiFi配置private WifiConfiguration getSavedWifiConfig(String ssid) {if (configuredNetworks == null || configuredNetworks.isEmpty()) {return null;}String targetSSID = "\"" + ssid + "\"";for (WifiConfiguration config : configuredNetworks) {if (targetSSID.equals(config.SSID)) {return config;}}return null;}// 连接WiFi的核心方法private void connect(String ssid, String password, String capabilities) {// 先检查是否已保存该WiFi(防止重复保存)WifiConfiguration savedConfig = getSavedWifiConfig(ssid);if (savedConfig != null) {connectToSavedWifi(ssid);return;}// 未保存:创建新配置并保存WifiConfiguration newConfig = new WifiConfiguration();newConfig.SSID = "\"" + ssid + "\"";// 根据加密类型设置配置if (capabilities.contains("WEP")) {newConfig.wepKeys[0] = "\"" + password + "\"";newConfig.wepTxKeyIndex = 0;newConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);newConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);} else if (capabilities.contains("WPA")) {newConfig.preSharedKey = "\"" + password + "\"";} else {// 开放网络newConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);}// 添加配置并连接int networkId = wifiManager.addNetwork(newConfig);if (networkId != -1) {// 保存配置wifiManager.saveConfiguration();// 启用该网络并断开其他网络wifiManager.disconnect();boolean isConnected = wifiManager.enableNetwork(networkId, true);wifiManager.reconnect();if (isConnected) {Toast.makeText(this, "正在连接:" + ssid, Toast.LENGTH_SHORT).show();// 更新已保存列表configuredNetworks = wifiManager.getConfiguredNetworks();} else {// 连接失败:删除无效配置wifiManager.removeNetwork(networkId);wifiManager.saveConfiguration();Toast.makeText(this, "连接失败,请检查密码", Toast.LENGTH_SHORT).show();}} else {Toast.makeText(this, "无法添加网络配置", Toast.LENGTH_SHORT).show();}}// 检查权限并启用WiFiprivate void checkPermissionsAndEnableWifi() {if (hasRequiredPermissions()) {wifiManager.setWifiEnabled(true);} else {requestPermissions();}}// 检查权限并扫描WiFiprivate void checkPermissionsAndScan() {if (hasRequiredPermissions()) {startWifiScan();} else {requestPermissions();}}// 检查必要权限private boolean hasRequiredPermissions() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {return ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)== PackageManager.PERMISSION_GRANTED;}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {return ContextCompat.checkSelfPermission(this, android.Manifest.permission.NEARBY_WIFI_DEVICES)== PackageManager.PERMISSION_GRANTED;}return true;}// 请求必要权限private void requestPermissions() {List<String> permissions = new ArrayList<>();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {permissions.add(android.Manifest.permission.ACCESS_FINE_LOCATION);}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {permissions.add(android.Manifest.permission.NEARBY_WIFI_DEVICES);}ActivityCompat.requestPermissions(this,permissions.toArray(new String[0]),PERMISSION_REQUEST_CODE);}// 处理权限请求结果@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == PERMISSION_REQUEST_CODE) {boolean hasPermission = true;for (int result : grantResults) {if (result != PackageManager.PERMISSION_GRANTED) {hasPermission = false;break;}}if (hasPermission) {if (wifiSwitch.isChecked() && !wifiManager.isWifiEnabled()) {wifiManager.setWifiEnabled(true);}} else {Toast.makeText(this, "需要位置/附近WiFi权限才能使用扫描功能", Toast.LENGTH_SHORT).show();wifiSwitch.setChecked(false);}}}// 开始扫描WiFiprivate void startWifiScan() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isForeground()) {Toast.makeText(this, "应用需要在前台才能扫描WiFi", Toast.LENGTH_SHORT).show();return;}boolean started = wifiManager.startScan();if (!started) {Toast.makeText(this, "扫描失败,请重试", Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "开始扫描...", Toast.LENGTH_SHORT).show();}}// 检查应用是否在前台private boolean isForeground() {ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();if (appProcesses == null) {return false;}final String packageName = getPackageName();for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {if (appProcess.processName.equals(packageName) &&appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {return true;}}return false;}// WiFi列表适配器(确保已连接标识正确显示)private class WifiListAdapter extends BaseAdapter {@Overridepublic int getCount() {return (sortedScanResults != null) ? sortedScanResults.size() : 0;}@Overridepublic Object getItem(int position) {return (sortedScanResults != null) ? sortedScanResults.get(position) : null;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView == null) {convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.wifi_list_item, parent, false);// 使用ViewHolder模式优化性能holder = new ViewHolder();holder.ssidText = convertView.findViewById(R.id.ssid_text);holder.strengthText = convertView.findViewById(R.id.strength_text);holder.securityText = convertView.findViewById(R.id.security_text);holder.connectedTv = convertView.findViewById(R.id.tv_connected);holder.savedIndicator = convertView.findViewById(R.id.saved_indicator);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}if (sortedScanResults != null && position < sortedScanResults.size()) {ScanResult result = sortedScanResults.get(position);boolean isConnected = result.SSID.equals(currentConnectedSSID);boolean isSaved = isWifiSaved(result.SSID);// 显示SSID和已连接标识holder.ssidText.setText(result.SSID);holder.connectedTv.setVisibility(isConnected ? View.VISIBLE : View.GONE);// 显示已保存标识(可选)holder.savedIndicator.setVisibility(isSaved && !isConnected ? View.VISIBLE : View.GONE);// 信号强度holder.strengthText.setText(String.format("信号强度: %d dBm", result.level));// 加密方式String security = "开放网络";if (result.capabilities.contains("WPA3")) {security = "WPA3";} else if (result.capabilities.contains("WPA2")) {security = "WPA2";} else if (result.capabilities.contains("WPA")) {security = "WPA";} else if (result.capabilities.contains("WEP")) {security = "WEP";}holder.securityText.setText("加密方式: " + security);}return convertView;}// ViewHolder内部类private class ViewHolder {TextView ssidText;TextView strengthText;TextView securityText;TextView connectedTv;TextView savedIndicator;}}
}

一、WiFi 状态控制接口

  1. setWifiEnabled(boolean enabled)
    • 作用:开启或关闭 WiFi 功能。
    • 参数enabled=true 开启,enabled=false 关闭。
    • 返回值booleantrue 表示操作成功(系统可能因权限 / 策略限制返回 false)。
    • 权限:需 android.permission.CHANGE_WIFI_STATE 权限。
    • 场景:用户点击 WiFi 开关时调用,控制硬件开关状态。
  2. getWifiState()
    • 作用:获取当前 WiFi 的状态。
    • 返回值int,可能值:
      • WIFI_STATE_DISABLED(已关闭)、WIFI_STATE_ENABLED(已开启);
      • WIFI_STATE_DISABLING(正在关闭)、WIFI_STATE_ENABLING(正在开启);
      • WIFI_STATE_UNKNOWN(未知状态)。
    • 权限:需 android.permission.ACCESS_WIFI_STATE 权限。
    • 场景:监听 WiFi 开关状态变化,更新 UI(如开关按钮状态)。
  3. isWifiEnabled()
    • 作用:快速判断 WiFi 是否处于 “已开启” 状态(简化版 getWifiState())。
    • 返回值booleantrue 表示已开启,false 表示关闭或正在切换。
    • 权限:需 android.permission.ACCESS_WIFI_STATE 权限。
    • 场景:发起扫描前检查 WiFi 是否开启,避免无效操作。

二、WiFi 扫描相关接口

  1. startScan()
    • 作用:触发系统扫描周围可用的 WiFi 网络(2.4G/5G 频段)。
    • 返回值booleantrue 表示扫描请求已提交(不代表扫描成功),false 表示失败(如后台限制、硬件忙)。
    • 权限
      • Android 12+ 需 android.permission.NEARBY_WIFI_DEVICES
      • Android 10-11 需 android.permission.ACCESS_FINE_LOCATION
      • 且应用需在前台(Android M+ 限制)。
    • 场景:用户点击 “刷新 WiFi 列表” 时调用,触发扫描并等待结果。
  2. getScanResults()
    • 作用:获取最近一次扫描的结果(列表形式)。
    • 返回值List,每个 ScanResult 包含 SSID(WiFi 名称)、level(信号强度,dBm)、capabilities(加密方式)等信息。
    • 权限:同 startScan()(需位置 / 附近 WiFi 权限)。
    • 注意:结果是缓存的,需配合 SCAN_RESULTS_AVAILABLE_ACTION 广播监听扫描完成后再调用。
    • 场景:扫描完成后,解析并展示 WiFi 列表(如信号强度排序、加密方式标识)。

三、WiFi 连接与配置管理接口

  1. addNetwork(WifiConfiguration config)
    • 作用:向系统添加一个新的 WiFi 配置(SSID、密码、加密方式等),用于后续连接。
    • 参数WifiConfiguration 对象,需设置 SSID(带双引号,如 "MyWiFi")、加密方式(WPA/WEP/ 开放)、密码等。
    • 返回值int,新增配置的唯一标识 networkId-1 表示添加失败)。
    • 权限:需 android.permission.CHANGE_WIFI_STATE 权限。
    • 场景:首次连接新 WiFi 时,创建配置并添加到系统(为 “保存功能” 基础)。
  2. enableNetwork(int networkId, boolean disableOthers)
    • 作用:启用指定 networkId 的 WiFi 配置,尝试连接该网络。
    • 参数
      • networkId:通过 addNetwork()getConfiguredNetworks() 获取的配置 ID;
      • disableOtherstrue 表示断开其他网络,优先连接当前配置。
    • 返回值booleantrue 表示启用请求成功(不代表连接成功)。
    • 权限:需 android.permission.CHANGE_WIFI_STATE 权限。
    • 场景:连接已保存的 WiFi 时(直接用 networkId),或新配置添加后触发连接。
  3. getConfiguredNetworks()
    • 作用:获取系统中所有已保存的 WiFi 配置列表。
    • 返回值List,包含已保存网络的 SSIDnetworkId、加密方式等信息。
    • 权限:需 android.permission.ACCESS_WIFI_STATE 权限。
    • 场景:判断某个 WiFi 是否已保存(如 “已保存网络直接连接” 功能)、展示用户的历史连接记录。

四、连接状态信息接口

  1. getConnectionInfo()

    • 作用:获取当前已连接 WiFi 的详细信息。

    • 返回值

      WifiInfo 对象,包含:

      • getSSID():当前连接的 WiFi 名称(可能带双引号,需处理);
      • getNetworkId():当前连接的配置 ID;
      • getRssi():当前信号强度(dBm);
      • getIpAddress():设备获取的 IP 地址等。
    • 权限:需 android.permission.ACCESS_WIFI_STATE 权限。

    • 场景:展示 “当前连接的 WiFi 名称及信号强度”、判断是否已连接目标网络。

关键广播(配合接口使用)

  • WIFI_STATE_CHANGED_ACTION:监听 WiFi 开关状态变化(配合 getWifiState() 使用)。
  • SCAN_RESULTS_AVAILABLE_ACTION:监听扫描结果是否更新(配合 getScanResults() 使用)。
  • NETWORK_STATE_CHANGED_ACTION:监听 WiFi 连接状态变化(如连接成功 / 失败、断开连接,配合 getConnectionInfo() 使用)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95376.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95376.shtml
英文地址,请注明出处:http://en.pswp.cn/web/95376.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【DSP28335 入门教程】定时器中断:为你的系统注入精准的“心跳”

大家好&#xff0c;欢迎来到 DSP28335 的核心精讲系列。我们已经掌握了如何通过外部中断来响应“外部事件”&#xff0c;但系统内部同样需要一个精准的节拍器来处理“内部周期性任务”。单纯依靠 DELAY_US() 这样的软件延时&#xff0c;不仅精度差&#xff0c;而且会在延时期间…

从零开始:用代码解析区块链的核心工作原理

区块链技术被誉为信任的机器&#xff0c;它正在重塑金融、供应链、数字身份等众多领域。但对于许多开发者来说&#xff0c;它仍然像一个神秘的黑盒子。今天&#xff0c;我们将抛开炒作的泡沫&#xff0c;深入技术本质&#xff0c;用大约100行Python代码构建一个简易的区块链&am…

网络通信IP细节

目录 1.通信的NAT技术 2.代理服务器 3.内网穿透和内网打洞 1.通信的NAT技术 NAT技术产生的背景是我们为了解决IPV4不够用的问题&#xff0c;NAT在通信的时候可以对IP将私网IP转化为公网IP&#xff0c;全局IP要求唯一&#xff0c;但是私人IP不是唯一的。 将报文发给路由器进行…

国内真实的交换机、路由器和分组情况

一、未考虑拥挤情况理想状态的网络通信 前面我对骨干网&#xff1a; 宜春城区SDH网图分析-CSDN博客 数据链路层MAC传输&#xff1a; 无线通信网卡底层原理&#xff08;Inter Wi-Fi AX201&#xff09;_ax201ngw是cnvio转pci-e-CSDN博客 物理层、数据链路层、网络层及传输层…

atomic常用类方法

Java中的java.util.concurrent.atomic包提供了多种原子操作工具类&#xff0c;以下是核心类及其方法&#xff1a;‌1. AtomicBoolean‌‌方法‌&#xff1a;get()&#xff1a;获取当前值set(boolean newValue)&#xff1a;强制设置值compareAndSet(boolean expect, boolean upd…

算法题打卡力扣第3题:无重复字符的最长子串(mid)

文章目录题目描述解法一&#xff1a;暴力解解法二&#xff1a;滑动窗口题目描述 解法一&#xff1a;暴力解 遍历每一个可能的子串&#xff0c;然后逐一判断每个子串中是否有重复字符。 具体步骤&#xff1a; 使用两层嵌套循环来生成所有子串的起止位置&#xff1a; 外层循环 i…

HTML5 简介和基础骨架

一、HTML5 简介HTML5 是 HTML&#xff08;超文本标记语言&#xff09;的第五个主要版本&#xff0c;于 2014 年 10 月由 W3C&#xff08;万维网联盟&#xff09;正式发布。它不仅是对 HTML4.01 和 XHTML 的升级&#xff0c;更是一套完整的 Web 技术标准&#xff0c;包含了新的标…

.NET技术深度解析:现代企业级开发指南

每日激励&#xff1a; “不要一直责怪过去的自己&#xff0c;他曾经站在雾里也很迷茫” &#x1f31f; Hello&#xff0c;我是蒋星熠Jaxonic&#xff01; &#x1f308; 在浩瀚无垠的技术宇宙中&#xff0c;我是一名执着的星际旅人&#xff0c;用代码绘制探索的轨迹。 &#x1…

苹果手机文本转音频,自行制作背诵素材

当你在学习一段专业内容或者背诵重要知识点时&#xff0c;是不是有时会觉得眼睛看久了疲惫&#xff0c;而且记忆效果也不太理想呢&#xff1f;利用手头的苹果手机或iPad&#xff0c;你可以轻松将文本内容生成音频文件&#xff0c;然后随时随地反复听&#xff0c;这对于备考人士…

电子电子技术知识------MOSFET管

电子电子技术知识------MOSFET管前言一、结构与符号二、工作原理1.小功率MOSFET&#xff08;横向导电&#xff09;2.电力MOS管三、基本特性总结前言 MOSFET是电力场效应晶体管的英文简写&#xff0c;又称功率mos管&#xff0c;mos管 一、结构与符号 二、工作原理 1.小功率M…

仿真波导中超短脉冲传输中的各种非线性效应所产生的超连续谱

在波导中&#xff0c;超短脉冲传输时会受到各种非线性效应的影响&#xff0c;从而产生超连续谱。这些非线性效应包括自相位调制&#xff08;SPM&#xff09;、交叉相位调制&#xff08;XPM&#xff09;、四波混频&#xff08;FWM&#xff09;等。基于MATLAB的仿真程序&#xff…

docker-compose的使用

目录 1-查看容器 2-查看docker镜像 3-运行两个容器 4-进入idea 编写docker-compose文件中的内容 5-编写配置文件 6-运行 7-docker-compose中的一些命令 启动服务 关闭服务 查看正在运行的容器 查看日志 重构新的服务 指令docker-compose 文件名 停止已运行的服务 启动 重启 1-查…

搭建分布式Hadoop集群[2025] 实战笔记

文章目录 一、实战目标 二、集群规划 1. 集群拓扑结构 2. 角色分配 说明: 三、环境准备 1. 修改 SSH 端口(安全加固) 操作步骤(所有节点执行): 2. FinalShell 连接配置 3. 防火墙配置 启动并配置 firewalld: 关闭并禁用防火墙(生产环境建议精细配置,测试环境可关闭):…

【自记录】Ubuntu20.04下Python自编译

因为需要新的Python版本&#xff0c;但是我们不希望修改系统原生的Python版本避免某些系统应用无法启动&#xff0c;因此自建一个干净的路径引入Python。 1.编译 以下在aarch64下测试&#xff0c;x64下可能有差异 必须把相关的devel包安装完毕&#xff0c;否则python可能缺功能…

Linux - 进程切换

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录进…

机器算法(五)模型选择与调优

一 交叉验证1 保留交叉验证HoldOutholdOut Cross-validation(Train-Test Split)在这种交叉验证技术中&#xff0c;整个技术集被随机划分为训练集和验证集。根据经验法则&#xff0c;整个数据集的近70%被用作训练集&#xff0c;其余30%被用作验证集&#xff0c;也就是最常使用的…

Ubuntu 服务器实战:Docker 部署 Nextcloud+ZeroTier,打造可远程访问的个人云

本次部署基于 Ubuntu 系统&#xff08;桌面版 / Server 版通用&#xff0c;核心操作一致&#xff09;&#xff0c;硬件配置参考如下&#xff0c;低配置主机可顺畅运行&#xff1a; ubuntu服务器配置如下 硬件类型具体型号/参数CPUIntel Core i3-6100T内存条8GB&#xff08;DD…

移动硬盘删除东西后,没有释放空间

请按照以下步骤&#xff0c;从最简单、最常见的原因开始排查和解决&#xff1a;主要原因和解决方案1. 检查操作系统回收站 (最常见原因&#xff01;)这是最容易被忽略的一点。当您直接在外接移动硬盘上删除文件时&#xff0c;文件并不会直接消失&#xff0c;而是被移到了该移动…

spring boot驴友结伴游网站的设计与实现(代码+数据库+LW)

摘要 本文介绍了基于Spring Boot框架开发的驴友结伴游网站的设计与实现。该网站旨在为旅行爱好者提供一个便捷的平台&#xff0c;使他们能够轻松地寻找伙伴、预定酒店、参与活动以及分享旅行经历。系统主要分为两大模块&#xff1a;用户模块和管理员模块。用户可以通过注册账号…

人机之间的强交互与弱交互

人机交互不是简单的人机&#xff0c;其本质是人机环境系统的交互。在这个系统中&#xff0c;人和机器不是孤立的存在&#xff0c;而是在特定环境下相互影响、相互作用的一部分。人机之间的强交互与弱交互可以从以下几个方面来理解&#xff1a;1、人机强交互通常是指人与机器之间…