Android WIFI使用简述(上)https://developer.aliyun.com/article/1407536
③ 启动扫描
启动扫描之前要做的是权限的处理,在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))) { //扫描Wifi showMsg(wifiManager.startScan() ? "扫描Wifi中" : "开启扫描失败"); } else { showMsg("扫描设备需要此权限"); } });
最后就是扫描Wifi按钮的点击事件,同样是在initView()方法中添加,代码如下:
//扫描Wifi 按钮点击事件 binding.btnScanWifi.setOnClickListener(v -> { //是否打开Wifi if (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; } } //扫描Wifi showMsg(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() { @Override public 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() { @Override public void onAvailable(@NonNull Network network) { super.onAvailable(network); if (wifiConnectCallback != null) { wifiConnectCallback.onSuccess(network); } } @Override public 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() { @Override public 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 @Override public 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中进行注册监听
然后实现方法:
@Override public 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);
然后实现回调方法。
@Override public void onSuccess(Network network) { showMsg("连接成功"); } @Override public 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.MaterialToolbar android: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.TextInputLayout android: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.TextInputEditText android: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.MaterialButton android: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.MaterialButton android: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