Android WIFI使用简述

news/2024/11/24 23:42:59/

前言

  随着Android版本的更新,目前最新的版本是Android 13,并且已经有部分国产手机更新了此版本,对于Android开发者来说,变化其实不那么大,而对于本文章来说就有一些变化。

正文

  在Android 12版本中,增加了对于蓝牙操作的动态权限,而在Android 13中,增加了对于WIFI操作的动态权限,日常工作生活中,我们用到WIFI功能是很多的,例如手机、电脑、电视等设备。而使用WIFI是一回事,WIFI开发又是另一回事,和蓝牙是一个道理,它们之间也有很多相似的地方。

一、创建项目

  首先创建项目,这里我使用的Android Studio版本为Android Studio Electric Eel | 2022.1.1,创建一个名为Android13Wifi的项目。

在这里插入图片描述

项目创建好之后,最低的API为24对应Android 7,最高33对应Android 13,Gradle插件版本为:7.5。

二、配置项目

作为WIFI项目我们首先要配置项目的静态权限,在AndroidManifest.xml中增加如下代码:

    <uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

  在 Android 13 中,Google 将 Wi-Fi 扫描与位置相关内容分离, Android 13 为管理设备与周围 Wi-Fi 热点连接的应用添加 NEARBY_WIFI_DEVICES 运行时权限 (属于 NEARBY_DEVICES权限组),从而在不需要 ACCESS_FINE_LOCATION 权限的情况下,也可以让应用访问附近的 Wi-Fi 设备。

  这和Android 12中增加的三个蓝牙权限如出一辙,此前扫描蓝牙和WIFI需要定位权限一直是Google的痛点,也一直被诟病。

  所以对于仅需要连接 Wi-Fi 设备,但实际上并不需要了解设备位置的应用来说,以 Android 13 (33)为目标平台的应用现在可以通过 “neverForLocation” 属性来完善申请 NEARBY_WIFI_DEVICES 权限。

  同时我们还应该关注Android 13以下的设备使用,因此ACCESS_FINE_LOCATION权限也要配置,在AndroidManifest.xml中增加如下代码:

    <!--Android 6 ~ 12 使用定位权限--><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"tools:ignore="CoarseFineLocation" /><!--Android 13及以上使用权限--><uses-permissionandroid:name="android.permission.NEARBY_WIFI_DEVICES"android:usesPermissionFlags="neverForLocation"tools:targetApi="Tiramisu" />

然后可以开启ViewBinding,在app下的build.gradle的android{}闭包中增加如下代码:

    buildFeatures {viewBinding true    //开启ViewBinding}

添加位置如下图所示:

在这里插入图片描述

然后我们修改以下activity_main.xml,里面的代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:id="@+id/btn_open_wifi"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="8dp"android:layout_marginEnd="8dp"android:text="打开WIFI"app:layout_constraintEnd_toStartOf="@+id/btn_scan_wifi"app:layout_constraintHorizontal_bias="0.5"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btn_scan_wifi"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="8dp"android:layout_marginEnd="8dp"android:text="扫描WIFI"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.5"app:layout_constraintStart_toEndOf="@+id/btn_open_wifi"app:layout_constraintTop_toTopOf="@+id/btn_open_wifi" /><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_wifi"android:layout_width="0dp"android:layout_height="0dp"android:background="#EEE"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/btn_open_wifi" /></androidx.constraintlayout.widget.ConstraintLayout>

  就只有两个按钮(用于打开/关闭WIFI,扫描WIFI),一个列表(显示WIFI设备,连接WIFI)。现在xml已经有了,我们先搞定ViewBinding,修改MainActivity 的代码如下所示:

public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());}
}

下面就是写功能了,首先是WIFI的打开和关闭,在此之前需要获取WIFI的开关状态。

三、WIFI开关

在使用Wifi之前,我们首先要打开Wifi,而打开Wifi在不同的版本上方式不同,首先在MainActivity中声明变量

    private WifiManager wifiManager;//Wifi管理者private ActivityResultLauncher<Intent> openWifi;    //打开Wifi意图

然后通过系统Wifi服务获取wifiManager,在onCreate()方法中添加如下代码:

    wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

  通过wifiManager.getWifiState()可以得到Wifi的状态,所以可以在MainActivity中 checkWifiState() 方法,代码如下所示:

    public void checkWifiState() {String msg;switch (wifiManager.getWifiState()) {case WifiManager.WIFI_STATE_DISABLING:msg = "Wifi正在关闭";break;case WifiManager.WIFI_STATE_DISABLED:msg = "Wifi已经关闭";binding.btnOpenWifi.setText("打开Wifi");break;case WifiManager.WIFI_STATE_ENABLING:msg = "Wifi正在开启";break;case WifiManager.WIFI_STATE_ENABLED:msg = "Wifi已经开启";binding.btnOpenWifi.setText("关闭Wifi");break;default:msg = "没有获取到WiFi状态";break;}showMsg(msg);}

  这里我在Wifi开启和关闭的时候修改了按钮的文字,因为涉及到Android版本的判断,所以在MainActivity中增加isAndroidTarget() 方法,代码如下所示:

    private boolean isAndroidTarget(int targetVersion) {return Build.VERSION.SDK_INT >= targetVersion;}

通过这个方法我们只要传递目标Android版本进去即可,最后再增加一个showMsg()方法,用于弹出Toast进行提示。

    private void showMsg(CharSequence msg) {Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();}

  在Android10及以上版本打开蓝牙开关需要进行一个意图处理,这里我们通过Activity Result API来进行处理,在MainActivity中声明变量:

    private ActivityResultLauncher<Intent> openWifi;    //打开Wifi意图

然后新增一个registerIntent()方法,代码如下所示:

    private void registerIntent() {//打开Wifi开关openWifi = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {checkWifiState();});}

  在返回结果时,调用checkWifiState()方法检查Wifi的状态,registerIntent()方法需要在Activity创建之前进行调用,修改onCreate()方法,代码如下:

    @Overrideprotected void onCreate(Bundle savedInstanceState) {//注册意图registerIntent();super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());//通过Wifi服务获取wifi管理者对象wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);//初始化视图initView();}

  这里有一个initView()方法,在MainActivity中创建它,在此方法中将对于按钮的点击事件进行处理,代码如下所示:

    private void initView() {//打开/关闭Wifibinding.btnOpenWifi.setOnClickListener(v -> {//Android10及以上版本if (isAndroidTarget(Build.VERSION_CODES.Q)) {openWifi.launch(new Intent(Settings.Panel.ACTION_WIFI));} else {wifiManager.setWifiEnabled(!wifiManager.isWifiEnabled());checkWifiState();}});}

  这里在点击事件中判断了当前的Android版本,10及以上版本采用意图的方式,以下的版本采用wifiManager.setWifiEnabledAPI的方式,下面我们运行一下:

在这里插入图片描述

四、WIFI扫描

  WIFI开关搞定之后,我们来做WIFI的扫描,这里的WIFI扫描是通过广播来接收结果,结果对象是ScanResult,这个名字和蓝牙扫描的ScanResult一样,不要导错了包,扫描的结果以列表的形式展现,所以我们可以根据这个结果对象来写一个Wifi适配器,适配器中就显示Wifi的名称,状态,信号强度信息。这里会用到比较多的图片资源,用来标识信号强度等级的,从我的源码中去获取即可。

在这里插入图片描述

  根据Wifi的加密与否,分为两种:加密与开放,每一种有五个图标来分别表示不同的信号强度,这里我做了两个level-list,是wifi_level.xml和wifi_lock_level.xml,在代码中可以通过信号强度得到不同的level,然后根据加密状态设置level资源图标即可。

① Wifi适配器

要创建适配器,首先要创建item的xml,在layout下创建一个item_wifi_rv.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="1dp"android:background="@color/white"><TextViewandroid:id="@+id/tv_wifi_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="16dp"android:text="Wifi 名称"android:textColor="@color/black"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><TextViewandroid:id="@+id/tv_wifi_state"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="2dp"android:layout_marginBottom="16dp"android:text="Wifi 状态"android:textSize="12sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_wifi_name" /><ImageViewandroid:id="@+id/iv_signal"android:layout_width="36dp"android:layout_height="36dp"android:layout_marginEnd="16dp"app:layout_constraintBottom_toBottomOf="@+id/tv_wifi_state"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="@+id/tv_wifi_name"android:src="@drawable/wifi_level" />
</androidx.constraintlayout.widget.ConstraintLayout>

如我前面所说的,就是三个内容

在这里插入图片描述

下面写适配器,在com.llw.wifi下创建一个WifiAdapter类,代码如下所示:

public class WifiAdapter extends RecyclerView.Adapter<WifiAdapter.ViewHolder> {private final List<ScanResult> lists;public WifiAdapter(List<ScanResult> lists) {this.lists = lists;}@NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);return new ViewHolder(binding);}@Overridepublic void onBindViewHolder(@NonNull ViewHolder holder, int position) {//Wifi名称holder.binding.tvWifiName.setText(lists.get(position).SSID);//Wifi功能String capabilities = lists.get(position).capabilities;//Wifi状态标识 true:加密,false:开放boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");//Wifi状态描述String wifiState = wifiStateFlag ? "加密" : "开放";holder.binding.tvWifiState.setText(wifiState);//信号强度int imgLevel;int level = lists.get(position).level;if (level <= 0 && level >= -50) {imgLevel = 5;} else if (level < -50 && level >= -70) {imgLevel = 4;} else if (level < -70 && level >= -80) {imgLevel = 3;} else if (level < -80 && level >= -100) {imgLevel = 2;} else {imgLevel = 1;}//根据是否加密设置不同的图片资源holder.binding.ivSignal.setImageResource(wifiStateFlag ? R.drawable.wifi_lock_level : R.drawable.wifi_level);//设置图片等级holder.binding.ivSignal.setImageLevel(imgLevel);}@Overridepublic int getItemCount() {return lists.size();}public static class ViewHolder extends RecyclerView.ViewHolder {public ItemWifiRvBinding binding;public ViewHolder(@NonNull ItemWifiRvBinding itemWifiRvBinding) {super(itemWifiRvBinding.getRoot());binding = itemWifiRvBinding;}}
}

  这里就简单用了一下ViewBinding,核心的内容在onBindViewHolder()方法中,这里我们根据扫描结果进行三个内容的渲染。

② 扫描结果处理

下面我们回到MainActivity,声明变量:

    private final List<ScanResult> wifiList = new ArrayList<>();    //Wifi结果列表private WifiAdapter wifiAdapter;    //Wifi适配器

扫描成功或者适配都可以处理,在MainActivity中新增如下方法代码:

    private void scanSuccess() {//扫描成功:新的扫描结果wifiList.clear();wifiList.addAll(wifiManager.getScanResults());wifiAdapter.notifyDataSetChanged();}private void scanFailure() {// 处理失败:新的扫描没有成功,使用旧的扫描结果wifiList.clear();wifiList.addAll(wifiManager.getScanResults());wifiAdapter.notifyDataSetChanged();}

  你会发现两个方法的内容是一样的,但是含义不同,当然你如果了解的话,就使用一个方法也行。下面在MainActivity中新增如下代码:

    /*** Wifi扫描广播接收器*/private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context c, Intent intent) {boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);if (success) {scanSuccess();} else {scanFailure();}}};

通过接收广播来刷新数据,在MainActivity中再增加一个initScan(),代码如下所示:

    private void initScan() {IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);registerReceiver(wifiScanReceiver, intentFilter);}

在页面初始化之后就注册广播接收器,在onCreate()中调用。

在这里插入图片描述

下面需要配置一下适配器和RecyclerView,在initView()方法中增加如下代码:

	wifiAdapter = new WifiAdapter(wifiList);binding.rvWifi.setLayoutManager(new LinearLayoutManager(this));binding.rvWifi.setAdapter(wifiAdapter);

③ 启动扫描

  启动扫描之前要做的是权限的处理,在MainActivity中声明变量:

    private ActivityResultLauncher<String[]> requestPermission;     //请求权限意图

然后在registerIntent()方法中添加如下代码:

        requestPermission = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {if (Boolean.TRUE.equals(result.get(Manifest.permission.NEARBY_WIFI_DEVICES))|| Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) {//扫描WifishowMsg(wifiManager.startScan() ? "扫描Wifi中" : "开启扫描失败");} else {showMsg("扫描设备需要此权限");}});

最后就是扫描Wifi按钮的点击事件,同样是在initView()方法中添加,代码如下:

        //扫描Wifi 按钮点击事件binding.btnScanWifi.setOnClickListener(v -> {//是否打开Wifiif (wifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) return;//Android13及以上版本if (isAndroidTarget(Build.VERSION_CODES.TIRAMISU)) {if (!hasPermission(Manifest.permission.NEARBY_WIFI_DEVICES) && !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {requestPermission.launch(new String[]{Manifest.permission.NEARBY_WIFI_DEVICES, Manifest.permission.ACCESS_FINE_LOCATION});return;}} else {if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {requestPermission.launch(new String[]{Manifest.permission.ACCESS_FINE_LOCATION});return;}}//扫描WifishowMsg(wifiManager.startScan() ? "扫描Wifi中" : "开启扫描失败");});

  这里我在Android 13以上版本同时请求了定位和Wifi权限,如果不这么做的话,调用wifiManager.startScan()就会返回false,而在Android 13以下就只请求定位权限即可,这里还需要给MainActivity添加一个@SuppressLint("MissingPermission")注解,如下图所示:

在这里插入图片描述

  这样在api 33中使用wifi相关的api时就不会提示错误了,不过你得注意一点,就是你在使用之前确保权限已经获取到,否则会报错闪退。wifiManager.startScan()调用会启动系统扫描,通过系统在扫描结束后,会发出WifiManager.SCAN_RESULTS_AVAILABLE_ACTION的广播,当我们的接收者接收到这个广播的时候,通过WifiManager的getScanResults()就能获取到扫描结果的集合了。如果扫描失败就会返回之前的值,成功最近最新的值。

下面我们运行看一下:

在这里插入图片描述

  这样看起来还是不错吧,现在有一个问题,就是这个扫描的wifi没有排序,同时没有wifi名称的我们应该过滤掉。

④ 排序与过滤

  现在我们已经知道扫描成功和失败的结果区别了,所以就合并以下,同时增加过滤掉空名称的WIFI兵器信号强度进行排序,修改一下广播接收器中的代码,如下所示:

    private final BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context c, Intent intent) {boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);Log.e(TAG, "onReceive: " + (success ? "成功" : "失败") );//处理扫描结果wifiList.clear();for (ScanResult scanResult : wifiManager.getScanResults()) {if (!scanResult.SSID.isEmpty()) {wifiList.add(scanResult);}}sortByLevel(wifiList);wifiAdapter.notifyDataSetChanged();}};

这里的scanSuccess()和scanFailure()就可以删掉了,再增加一个sortByLevel()方法,代码如下:

    private void sortByLevel(List<ScanResult> list) {Collections.sort(list, (lhs, rhs) -> rhs.level - lhs.level);}

下面我们再运行一下看看:

在这里插入图片描述

五、WIFI连接

  Wifi的连接相对扫描来说复杂一点,假设现在有三个Wifi,分别是A、B、C。刚开始三个Wifi都没有连接过,在第一次连接A的时候,我们需要输入Wifi密码,密码正确才会建立连接,连接成功后,我们连接B,同样输入密码,此时A就会断开,连接B成功,此时我再转头去连接A,因为之前成功连接过,有保存记录,所以再连接A的时候直接连接就可以了,不再需要密码了。

① Wifi连接工具类

  基于这个情况我写了一个工具类,在com.llw.wifi下新建一个EasyWifi类,代码如下所示:

public class EasyWifi {private static final String TAG = EasyWifi.class.getSimpleName();private final ConnectivityManager connectivityManager;//连接管理者private final WifiManager wifiManager;//Wifi管理者private WifiConnectCallback wifiConnectCallback;@SuppressLint("StaticFieldLeak")private static volatile EasyWifi mInstance;private final Context mContext;public EasyWifi(Context context) {mContext = context;wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);}public static EasyWifi initialize(Context context) {if (mInstance == null) {synchronized (EasyWifi.class) {if (mInstance == null) {mInstance = new EasyWifi(context);}}}return mInstance;}public void setWifiConnectCallback(WifiConnectCallback wifiConnectCallback) {this.wifiConnectCallback = wifiConnectCallback;}/*** 连接Wifi** @param scanResult 扫描结果* @param password   密码*/public void connectWifi(ScanResult scanResult, String password) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {connectByNew(scanResult.SSID, password);} else {connectByOld(scanResult, password);}}/*** Android 10 以下使用** @param scanResult 扫描结果* @param password   密码*/private void connectByOld(ScanResult scanResult, String password) {String ssid = scanResult.SSID;boolean isSuccess;WifiConfiguration configured = isExist(ssid);if (configured != null) {//在配置表中找到了,直接连接isSuccess = wifiManager.enableNetwork(configured.networkId, true);} else {WifiConfiguration wifiConfig = createWifiConfig(ssid, password, getCipherType(scanResult.capabilities));int netId = wifiManager.addNetwork(wifiConfig);isSuccess = wifiManager.enableNetwork(netId, true);}Log.d(TAG, "connectWifi: " + (isSuccess ? "成功" : "失败"));}/*** Android 10及以上版本使用此方式连接Wifi** @param ssid     名称* @param password 密码*/@SuppressLint("NewApi")private void connectByNew(String ssid, String password) {WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder().setSsid(ssid).setWpa2Passphrase(password).build();//网络请求NetworkRequest request = new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED).setNetworkSpecifier(wifiNetworkSpecifier).build();//网络回调处理ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {@Overridepublic void onAvailable(@NonNull Network network) {super.onAvailable(network);if (wifiConnectCallback != null) {wifiConnectCallback.onSuccess(network);}}@Overridepublic void onUnavailable() {super.onUnavailable();if (wifiConnectCallback != null) {wifiConnectCallback.onFailure();}}};//请求连接网络connectivityManager.requestNetwork(request, networkCallback);}@SuppressLint("NewApi")private void connectBySug(String ssid, String password) {WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder().setSsid(ssid).setWpa2Passphrase(password).setIsAppInteractionRequired(true).build();List<WifiNetworkSuggestion> suggestionList = new ArrayList<>();suggestionList.add(suggestion);int status = wifiManager.addNetworkSuggestions(suggestionList);if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {return;}IntentFilter intentFilter = new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (!intent.getAction().equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {return;}}};mContext.registerReceiver(wifiScanReceiver, intentFilter);}/*** 创建Wifi配置** @param ssid     名称* @param password 密码* @param type     类型*/private WifiConfiguration createWifiConfig(String ssid, String password, WifiCapability type) {WifiConfiguration config = new WifiConfiguration();config.allowedAuthAlgorithms.clear();config.allowedGroupCiphers.clear();config.allowedKeyManagement.clear();config.allowedPairwiseCiphers.clear();config.allowedProtocols.clear();config.SSID = "\"" + ssid + "\"";WifiConfiguration configured = isExist(ssid);if (configured != null) {wifiManager.removeNetwork(configured.networkId);wifiManager.saveConfiguration();}//不需要密码的场景if (type == WifiCapability.WIFI_CIPHER_NO_PASS) {config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);//以WEP加密的场景} else if (type == WifiCapability.WIFI_CIPHER_WEP) {config.hiddenSSID = true;config.wepKeys[0] = "\"" + password + "\"";config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);config.wepTxKeyIndex = 0;//以WPA加密的场景,自己测试时,发现热点以WPA2建立时,同样可以用这种配置连接} else if (type == WifiCapability.WIFI_CIPHER_WPA) {config.preSharedKey = "\"" + password + "\"";config.hiddenSSID = true;config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);config.status = WifiConfiguration.Status.ENABLED;}return config;}/*** 网络是否连接*/public static boolean isNetConnected(ConnectivityManager connectivityManager) {return connectivityManager.getActiveNetwork() != null;}/*** 连接网络类型是否为Wifi*/public static boolean isWifi(ConnectivityManager connectivityManager) {if (connectivityManager.getActiveNetwork() == null) {return false;}NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());if (networkCapabilities != null) {return false;}return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);}/*** 配置表是否存在对应的Wifi配置* @param SSID* @return*/@SuppressLint("MissingPermission")private WifiConfiguration isExist(String SSID) {List<WifiConfiguration> existingConfigs = wifiManager.getConfiguredNetworks();for (WifiConfiguration existingConfig : existingConfigs) {if (existingConfig.SSID.equals("\"" + SSID + "\"")) {return existingConfig;}}return null;}private WifiCapability getCipherType(String capabilities) {if (capabilities.contains("WEB")) {return WifiCapability.WIFI_CIPHER_WEP;} else if (capabilities.contains("PSK")) {return WifiCapability.WIFI_CIPHER_WPA;} else if (capabilities.contains("WPS")) {return WifiCapability.WIFI_CIPHER_NO_PASS;} else {return WifiCapability.WIFI_CIPHER_NO_PASS;}}/*** wifi连接回调接口*/public interface WifiConnectCallback {void onSuccess(Network network);void onFailure();}public enum WifiCapability {WIFI_CIPHER_WEP, WIFI_CIPHER_WPA, WIFI_CIPHER_NO_PASS}
}

  这里对于Wifi的处理,主要是连接方面的,你当然也可以把扫描wifi放进来,对于wifi的连接,需要区分版本进行不同的处理,Android 10 及以上和Android 10以下是不同的方式,下面我们来使用这个工具类。

② 适配器点击处理

  下面在WifiAdapter中增加一个接口,代码如下所示:

    public interface OnItemClickListener {void onItemClick(int position);}

然后提供一个set方法,供使用的地方进行回调处理,代码如下:

    public void setOnItemClickListener(OnItemClickListener listener) {this.listener = listener;}

然后在onCreateViewHolder()方法中添加接口方法的使用,代码如下:

    @NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {ItemWifiRvBinding binding = ItemWifiRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);ViewHolder viewHolder = new ViewHolder(binding);//添加视图点击事件binding.getRoot().setOnClickListener(v -> {if (listener != null) {listener.onItemClick(viewHolder.getAdapterPosition());}});connectivityManager = (ConnectivityManager) parent.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);return viewHolder;}

最后回到MainActivity中进行注册监听

在这里插入图片描述

在这里插入图片描述

然后实现方法:

    @Overridepublic void onItemClick(int position) {ScanResult scanResult = wifiList.get(position);//获取Wifi扫描结果String capabilities = scanResult.capabilities;//Wifi状态标识 true:加密,false:开放boolean wifiStateFlag = capabilities.contains("WEP") || capabilities.contains("PSK") || capabilities.contains("EAP");if (wifiStateFlag) {} else {}}

  在这个方法中我们根据是否需要密码进行不同的处理,先看不需要密码的处理,我们这里需要使用工具类,在MainActivity中声明变量:

    private EasyWifi easyWifi;

然后在onCreate()方法中进行初始化和设置连接监听。

	easyWifi = EasyWifi.initialize(this);easyWifi.setWifiConnectCallback(this);

在这里插入图片描述

然后实现回调方法。

    @Overridepublic void onSuccess(Network network) {showMsg("连接成功");}@Overridepublic void onFailure() {showMsg("连接失败");}

  这里我们只是提示一下连接成功和失败。现在就是不需要密码时的处理了,在修改适配器Item点击事件中的if判断,代码如下:

	if (wifiStateFlag) {} else {easyWifi.connectWifi(scanResult,"");}

这里不需要密码,而需要密码则麻烦一些,我们需要写一个弹窗来输入密码。

③ 密码弹窗

首先我们需要创建弹窗所需要的布局文件,在layout下新建一个dialog_connect_wifi.xml文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.appbar.MaterialToolbarandroid:id="@+id/materialToolbar"android:layout_width="0dp"android:layout_height="wrap_content"android:background="@color/white"android:minHeight="?attr/actionBarSize"android:theme="?attr/actionBarTheme"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:title="Wifi名称"app:titleCentered="true"app:titleTextColor="@color/black" /><com.google.android.material.textfield.TextInputLayoutandroid:id="@+id/pwd_layout"style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginEnd="16dp"android:textColorHint="#989898"app:boxStrokeColor="@color/black"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/materialToolbar"><com.google.android.material.textfield.TextInputEditTextandroid:id="@+id/et_pwd"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="密码"android:inputType="textPassword|textCapCharacters"android:lines="1"android:singleLine="true" /></com.google.android.material.textfield.TextInputLayout><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_cancel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="取消"app:cornerRadius="24dp"app:layout_constraintEnd_toStartOf="@+id/btn_connect"app:layout_constraintHorizontal_bias="0.5"app:layout_constraintStart_toStartOf="@+id/pwd_layout"app:layout_constraintTop_toTopOf="@+id/btn_connect" /><com.google.android.material.button.MaterialButtonandroid:id="@+id/btn_connect"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:layout_marginBottom="16dp"android:text="连接"app:cornerRadius="24dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="@+id/pwd_layout"app:layout_constraintHorizontal_bias="0.5"app:layout_constraintStart_toEndOf="@+id/btn_cancel"app:layout_constraintTop_toBottomOf="@+id/pwd_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

这个布局很简单,就一个输入框,两个按钮,下面我们回到MainActivity中,增加如下方法代码:

    private void showConnectWifiDialog(ScanResult scanResult) {BottomSheetDialog dialog = new BottomSheetDialog(this);DialogConnectWifiBinding binding = DialogConnectWifiBinding.inflate(LayoutInflater.from(this), null, false);binding.materialToolbar.setTitle(scanResult.SSID);binding.btnCancel.setOnClickListener(v -> dialog.dismiss());binding.btnConnect.setOnClickListener(v -> {String password = binding.etPwd.getText().toString().trim();if (password.isEmpty()) {showMsg("请输入密码");return;}easyWifi.connectWifi(scanResult, password);dialog.dismiss();});dialog.setContentView(binding.getRoot());dialog.show();}

  这个方法就是显示密码输入弹窗,当输入密码之后就连接wifi,连接过程中就会触发之前工具类中的回调,下面我们需要调用这个连接方法,还是之前的那个if语句,代码如下所示:

        if (wifiStateFlag) {showConnectWifiDialog(scanResult);} else {easyWifi.connectWifi(scanResult,"");}

  现在可以运行了,因为Wifi连接涉及到隐私信息,所以我就不做动图演示了,连接成功之后会有提示,然后你打开系统Wifi页面会看到如下图所示的:

在这里插入图片描述

  你会看到这里连接的wifi下面提示了是通过Android13Wifi这个软件进行的wifi连接,当我们的程序被杀死,wifi就会断连,这是因为我们走的不是系统的wifi连接的方式。

六、源码

  文章中的wifi使用还是比较浅显的,简单了解一下,而如果你是专门从事WIFI应用开发的话,则需要花心思去研究了,不能流于表面,或者全部靠别人来帮你解决,能帮你的只有自己,山高水长,后会有期~

如果对你有帮助的话,不妨Star一下~

源码地址 :Android13Wifi


http://www.ppmy.cn/news/22836.html

相关文章

Kafka生成者/消费组详解

什么是 KafkaKafka 是由 Linkedin 公司开发的&#xff0c;它是一个分布式的&#xff0c;支持多分区、多副本&#xff0c;基于 Zookeeper 的分布式消息流平台&#xff0c;它同时也是一款开源的基于发布订阅模式的消息引擎系统。Kafka 的基本术语消息&#xff1a;Kafka 中的数据单…

【Kubernetes 企业项目实战】06、基于 Jenkins+K8s 构建 DevOps 自动化运维管理平台(中)

目录 一、基于 Jenkinsk8sGitDocker Hub 等技术链构建企业级 DevOps 容器云平台 1.1 安装 Jenkins 1.1.1 安装 nfs 服务 1.1.2 在 kubernetes 中部署 jenkins 1.2 配置 Jenkins ​1.2.1 获取管理员密码 1.2.2 安装插件 1.2.3 创建第一个管理员用户 1.3 测试 jenkins 的…

拍电影、电视剧需要什么资质?

一、拍电影所需要的资质 根据相关法律法规的规定&#xff0c;电影的摄制、发行、放映均需要具有相应资质方可进行。 01 关于电影摄制的资质 根据《电影管理条例&#xff08;2001年&#xff09;》第九条规定&#xff1a;申请设立电影制片单位&#xff0c;由所在地省、自治区…

Linux破解root密码

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Linux操作…

【JavaSE】fail-fast与fail-safe源码分析

文章目录1. fail-fast与fail-safe概述2. fail-fast源码分析3. fail-safe源码分析4. 总结1. fail-fast与fail-safe概述 快速失败(fail-fast)&#xff0c;快速失败是Java集合的一种错误检测机制。 出现场景&#xff1a;线程A在使用迭代器遍历一个集合对象的时候&#xff0c;线程…

《计算机构造与解释》读书笔记(5)

文章目录1. 写在最前面2. 流2.1 流作为延时的表2.1.1 流实现的行为方式2.1.2 delay 和 force 的实现2.2 无穷流2.2.1 隐式地定义流2.3 流计算模式的使用2.3.1 系统地将迭代操作方式表示为流过程2.3.2 序对的无穷流2.3.3 将流作为信号2.4 流和延时求值2.4.1 规范求值序2.5 函数式…

java基于springboot+vue的企业员工工资考勤系统

随着我国改革开发和国家政策的开发等一系列优惠条件的开放&#xff0c;我国的高校数量也是在不断的增加&#xff0c;每个高校都有很多的员工&#xff0c;每个员工的工资又各不相同&#xff0c;如何能够管理这些庞大的工资数据&#xff0c;是很多高校在发放工资的时候都需要面临…

介绍JVM中OOM的8种类型

title: 介绍JVM中OOM的8种类型 date: 2019-11-17 16:00:00 tags: JVM OOMOutOfMemoryError categories:JVM java.lang.OutOfMemoryError Java heap space Java应用程序仅允许使用有限的内存&#xff0c;此限制是在应用程序启动期间指定的。Java内存被分为两个不同的区域&…