Android WIFI使用简述(下)

简介: Android WIFI使用简述(下)

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")注解,如下图所示:

cc12745a47454fbc9f0e2ce435c18bbc.png

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

下面我们运行看一下:

0e68a965af84432eb6eaed20da04a863.gif

  这样看起来还是不错吧,现在有一个问题,就是这个扫描的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);
    }


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

abbcbb60feeb4e2aba01f26cbf18bd01.gif


五、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中进行注册监听

d63eb65895f345b3bd8a6689efd2753f.png

9b7b692886cb4082a32843a397869eb5.png

然后实现方法:

    @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);


21d6c922f6b24da0b2a2737af61b2951.png

然后实现回调方法。

    @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页面会看到如下图所示的:

17c0c67e8e1d4f93bd0f1c966adad568.png

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


六、源码


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

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

源码地址 :Android13Wifi

相关文章
|
10天前
|
Android开发
Android 状态栏WiFi图标的显示逻辑
Android 状态栏WiFi图标的显示逻辑
34 0
|
9天前
|
Android开发
Android获取当前连接的wifi名称
Android获取当前连接的wifi名称
20 6
|
10天前
|
Android开发
android连接指定wifi
android连接指定wifi
19 0
|
10天前
|
Java Android开发
Android 9在连接以太网情况下 还能连接WiFi
Android 9在连接以太网情况下 还能连接WiFi
13 0
|
10天前
|
Android开发
Android12 ethernet和wifi共存
Android12 ethernet和wifi共存
24 0
|
10天前
|
Java Shell Android开发
Android11 有线网和wifi优先级设置
Android11 有线网和wifi优先级设置
18 0
|
10天前
|
Java Android开发 开发者
rk3399 android以太网和wifi共存
rk3399 android以太网和wifi共存
18 0
|
10天前
|
缓存 Java Android开发
Android 9.0 WiFi 扫描结果上报和获取流程
Android 9.0 WiFi 扫描结果上报和获取流程
11 0
|
10天前
|
Android开发
Android 获取Wifi开关状态、控制Wifi开关
Android 获取Wifi开关状态、控制Wifi开关
14 0
|
10天前
|
Android开发
android 获取wifi的协议标准
android 获取wifi的协议标准
17 0