Android Wi-Fi网络连接API

一、背景
从Android Q开始,原WiFi操作API部分被废弃,eg: enableNetwork、getConfiguredNetworks等。取而代之的是WifiNetworkSpecifier,WifiNetworkSuggestion等新API(主要从安全和用户体验的角度出发,废弃了之前太过底层的API)。
二、新版API简介
Android Q增加了对点对等连接的支持。此功能使您的应用可以通过使用 WifiNetworkSpecifier 描述所请求网络的属性来提示用户更改设备所连接的接入点。对等连接用于非网络提供目的,例如Chromecast和Google Home硬件等辅助设备的引导配置。

使用此API时,您将使用以下流程:

  • 使用创建Wi-Fi网络说明符 WifiNetworkSpecifier.Builder

  • 设置网络过滤器以匹配要连接的网络以及所需的凭据。

  • 决定的组合 SSID SSID pattern BSSID ,和 BSSID pattern 设置在每个请求之后,网络过滤器,必须符合以下要求:

  • 每个请求应当提供的至少一个 SSID SSID pattern BSSID ,或 BSSID pattern
  • 每个请求只能设置一个 SSID SSID pattern
  • 每个请求只能设置一个 BSSID BSSID pattern

    将说明符与 NetworkCallback 实例一起添加到网络请求中 以跟踪请求的状态。

    如果用户接受请求并且与网络的连接成功, NetworkCallback.onAvailable() 则在回调对象上调用。如果用户拒绝请求或者与网络的连接不成功, NetworkCallback.onUnavailable() 则在回调对象上调用。

    点对点连接不需要位置或Wi-Fi权限。发起连接到对等设备的请求会在同一设备上启动一个对话框,该设备的用户可以从该对话框接受连接请求。

    绕过用户批准

    一旦用户批准网络连接以响应来自特定应用的请求,该设备就存储对特定接入点的批准。如果应用程序再次发出连接到该访问点的特定请求,则设备将跳过用户批准阶段并自动连接到网络。如果用户选择在连接到API请求的网络时忘记网络,则会删除此应用程序和网络组合的存储批准,并且应用程序的任何将来请求都需要再次由用户批准。如果应用程序发出非特定(例如使用SSID或BSSID模式)请求,则用户将需要批准该请求。
    三、实现方式
    官方适用于对等连接的 WLAN 网络请求 API | Android Developers (google.cn)
    但试过没有成功,其他较多的实现方式主要有两种:
    一、手机WIFI设置界面

              startActivity(new Intent( android.provider.Settings.ACTION_WIFI_SETTINGS));
              startActivity(new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY));
    

    第二种方式就是降低API为28

    android {
        compileSdk 32
        defaultConfig {
            applicationId "com.zyd.androidwifi"
            minSdk 26
            targetSdk 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
    

    java实现示例:

    public void wifiBeforeQ(String wifiName, String wifiPwd) {
        if (mWifiManager == null) {
            Log.i(TAG, " ***** init first ***** ");
            return;
        String mWifiName = "\"" + wifiName + "\"";
         * 判断定位权限
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        //获取wifi列表
        List wifiList = mWifiManager.getConfiguredNetworks();
        boolean bFindInList = false;
        for (int i = 0; i < wifiList.size(); ++i) {
            WifiConfiguration wifiInfo0 = (WifiConfiguration) wifiList.get(i);
            // 先找到对应的wifi
            if (mWifiName.equals(wifiInfo0.SSID) || wifiName.equals(wifiInfo0.SSID)) {
                // 1、 先启动,可能已经输入过密码,可以直接启动
                Log.i(TAG, " set wifi 1 = " + wifiInfo0.SSID);
                doChange2Wifi(wifiInfo0.networkId);
                return;
        // 2、如果wifi还没有输入过密码,尝试输入密码,启动wifi
        if (!bFindInList) {
            WifiConfiguration wifiNewConfiguration = createWifiInfo(wifiName, wifiPwd);//使用wpa2的wifi加密方式
            int newNetworkId = mWifiManager.addNetwork(wifiNewConfiguration);
            if (newNetworkId == -1) {
                Log.e(TAG, "操作失败,需要您到手机wifi列表中取消对设备连接的保存");
            } else {
                doChange2Wifi(newNetworkId);
    

    第三种不降API实现方式(不过没有成功)

    @RequiresApi(api = Build.VERSION_CODES.Q)
    public void wifiAfterQ(String wifiName, String wifiPwd) {
        Log.i(TAG, " ***** changeToWifiAfterQ first ***** ");
        if (mWifiManager == null || connectivityManager == null) {
            Log.i(TAG, " ***** init first ***** ");
            return;
         * 判断定位权限
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        //获取wifi扫描列表
    //        List<ScanResult> wifiList = mWifiManager.getScanResults();
    //        ScanResult scan = null;
    //        for (ScanResult scanResult : wifiList) {
    //            if (wifiName.equals(scanResult.SSID)) {
    //                scan = scanResult;
    //                break;
    //            }
    //        }
        //扫描到了Wi-Fi
    //        if (null != scan) {
        //setSsidPattern/setSsid/setBssidPattern/setBssid should be invoked for specifier
        NetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
    //                        .setSsidPattern(new PatternMatcher("test", PatterMatcher.PATTERN_PREFIX))
                .setSsid(wifiName)
                .setWpa2Passphrase(wifiPwd)
    //                .setBssid(MacAddress.fromString(scan.BSSID))
                .build();
        NetworkRequest request =
                new NetworkRequest.Builder()
                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                        .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                        .setNetworkSpecifier(specifier)
                        .build();
        networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(@NonNull Network network) {
                super.onAvailable(network);
                Log.i(TAG, "onAvailable 切换到指定wifi成功"+network);
                connectivityManager.bindProcessToNetwork(network);
                connectivityManager.getNetworkInfo(network);
                NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
                boolean connected = networkInfo.isConnected();
                boolean available = networkInfo.isAvailable();
                NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
                Log.i(TAG, "onAvailable connected= "+connected);
                Log.i(TAG, "onAvailable available= "+available);
                Log.i(TAG, "onAvailable detailedState= "+detailedState);
            @Override
            public void onUnavailable() {
                super.onUnavailable();
                Log.i(TAG, "onUnavailable 切换到指定wifi失败");
                connectivityManager.unregisterNetworkCallback(networkCallback);
     // connectivityManager.registerNetworkCallback(request, networkCallback);
        connectivityManager.requestNetwork(request, networkCallback);
    //        }else{
    //            Log.e(TAG, "未找到目标Wi-Fi");
    //        }
    

    四、Wi-Fi Easy连接
    通过Android Q,您可以使用Easy Connect为对等设备配置Wi-Fi凭据,以替代已弃用的WPS。应用可以使用ACTION_PROCESS_WIFI_EASY_CONNECT_URIintent 将Easy Connect集成到其设置和配置流程中 。此意图需要URI。调用应用程序可以通过各种方法检索URI,包括从贴纸或显示器扫描QR码,或通过扫描蓝牙LE或NFC广告。

    URI可用后,您可以使用ACTION_PROCESS_WIFI_EASY_CONNECT_URIintent 配置对等设备的Wi-Fi凭据。这允许用户选择要共享的Wi-Fi网络并安全地传输凭证。

    Easy Connect不需要位置或Wi-Fi权限。

    注意:在使用此意图之前,应用程序必须通过调用验证设备是否支持Easy ConnectWifiManager.isEasyConnectSupported()

    五、Wi-Fi Direct连接API
    在WifiP2pConfig和WifiP2pManagerAPI类在Android Q的更新,以支持使用预定信息的快速连接建立功能的Wi-Fi直连。该信息通过侧通道共享,例如蓝牙或NFC。

    以下代码示例显示如何使用预定信息创建组:
    Kotlin语言写法

    val manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
    val channel = manager.initialize(this, mainLooper, null)
    // prefer 5G band for this group
    val config = WifiP2pConfig.Builder()
    .setNetworkName("networkName")
    .setPassphrase("passphrase")
    .enablePersistentMode(false)
    .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_5GHZ)
    .build()
    // create a non-persistent group on 5GHz
    manager.createGroup(channel, config, null)
    

    Java语言写法

    WifiP2pManager manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    Channel channel = manager.initialize(this, getMainLooper(), null);
    // prefer 5G band for this group
    WifiP2pConfig config = new WifiP2pConfig.Builder()
    .setNetworkName("networkName")
    .setPassphrase("passphrase")
    .enablePersistentMode(false)
    .setGroupOperatingBand(WifiP2pConfig.GROUP_OWNER_BAND_5GHZ)
    .build();
    // create a non-persistent group on 5GHz
    manager.createGroup(channel, config, null);
    

    要使用凭据加入组,请使用manager.createGroup()以下内容替换:

    Kotlin语言写法

    manager.connect(channel, config, null)