以编程方式连接到 Android Q 中的 Wifi

Connect to Wifi in Android Q programmatically

我有这个功能可以连接到 Wifi 网络,低于 Android 10 它工作正常,但是当我尝试 Android 10 时,我连接成功但是 WITHOUT互联网,我知道它是a bug in Android 10 but I found this application,它可以从Android 10 毫无问题地连接到wifi。 我被封锁了好几天。

我的函数:

private void connectToWifi(String ssid, String password)
    {
        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            try {
                Log.e(TAG,"connection wifi pre Q");
                WifiConfiguration wifiConfig = new WifiConfiguration();
                wifiConfig.SSID = "\"" + ssid + "\"";
                wifiConfig.preSharedKey = "\"" + password + "\"";
                int netId = wifiManager.addNetwork(wifiConfig);
                wifiManager.disconnect();
                wifiManager.enableNetwork(netId, true);
                wifiManager.reconnect();

            } catch ( Exception e) {
                e.printStackTrace();
            }
        } else {
            Log.e(TAG,"connection wifi  Q");

            WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
                .setSsid( ssid )
                .setWpa2Passphrase(password)
                    .build();

            NetworkRequest networkRequest = new NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                    .setNetworkSpecifier(wifiNetworkSpecifier)
                    .build();

             connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);


                 networkCallback = new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    super.onAvailable(network);

                     connectivityManager.bindProcessToNetwork(network);
                    Log.e(TAG,"onAvailable");
                }

                  @Override
                  public void onLosing(@NonNull Network network, int maxMsToLive) {
                      super.onLosing(network, maxMsToLive);
                      Log.e(TAG,"onLosing");
                  }

                  @Override
                public void onLost(Network network) {
                    super.onLost(network);
                    Log.e(TAG, "losing active connection");
                }

                @Override
                public void onUnavailable() {
                    super.onUnavailable();
                    Log.e(TAG,"onUnavailable");
                }
            };
            connectivityManager.requestNetwork(networkRequest,networkCallback);


        }
    }

如果你想通过互联网连接到 WiFi,你应该使用这种 NetworkRequest:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    .setNetworkSpecifier(wifiNetworkSpecifier)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
    .build();

此外,您需要为您的进程指定默认路由,以永久向连接的 WiFi AP 发出请求。只需将下一个方法的调用添加到 onAvaliable 下的 NetworkCallback,如下所示:

networkCallback = new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        createNetworkRoute(network, connectivityManager);
        }
    };
    if (connectivityManager!= null) connectivityManager.requestNetwork(request, networkCallback);

.

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private static void createNetworkRoute(Network network, ConnectivityManager connectivityManager) {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManager.bindProcessToNetwork(network);
     } else {
          ConnectivityManager.setProcessDefaultNetwork(network);
     }
 } 

不要忘记断开与绑定网络的连接:

connectivityManager.unregisterNetworkCallback(networkCallback);

最后,您可以在不同的库中找到最佳实践,例如 WifiUtils

如果您有 root 访问权限 (adb root):

  1. 手动连接到您选择的 Wifi 网络。

  2. 拉取这些 ADB 文件:

adb pull /data/misc/wifi/WifiConfigStore.xml
adb pull /data/misc/wifi/WifiConfigStore.xml.encrypted-checksum
  1. 保存在指定 Wifi 网络的文件夹中:
Ex:  GarageWifi
Ex:  BusinessWifi

复制到您选择的位置。 不要更改您提取的文件的名称。

  1. 每当您想连接到所需的 wifi 网络时:
adb push <location>\WifiConfigStore.xml /data/misc/wifi/
adb push <location>\WifiConfigStore.xml.encrypted-checksum /data/misc/wifi/

adb reboot

你可以试试wifisuggestionapi,我可以用它们连接。

final WifiNetworkSuggestion suggestion1 =
            new WifiNetworkSuggestion.Builder()
                    .setSsid("YOUR_SSID")
                    .setWpa2Passphrase("YOUR_PRE_SHARED_KEY")
                    .build();
    final List<WifiNetworkSuggestion> suggestionsList =
            new ArrayList<WifiNetworkSuggestion>();
    suggestionsList.add(suggestion1);

    WifiManager wifiManager =
            (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    int status = wifiManager.addNetworkSuggestions(suggestionsList);
    if (status == 0 ){
        Toast.makeText(this,"PSK network added",Toast.LENGTH_LONG).show();
        Log.i(TAG, "PSK network added: "+status);
    }else {
        Toast.makeText(this,"PSK network not added",Toast.LENGTH_LONG).show();
        Log.i(TAG, "PSK network not added: "+status);
    }

所以,我的解决方案是使用 targetSdkVersion 28 编译您的应用程序。 并连接到 wifi 使用此功能

connectToWifi(String ssid, String key)

目前只是一种解决方法,等待 Google 发布此错误的修复程序,有关此问题的更多信息已报告给 Google:issuetracker.google.com/issues/138335744

public void connectToWifi(String ssid, String key) {

Log.e(TAG, "connection wifi pre Q");
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = "\"" + ssid + "\"";
wifiConfig.preSharedKey = "\"" + key + "\"";
int netId = wifiManager.addNetwork(wifiConfig);
if (netId == -1) netId = getExistingNetworkId(wifiConfig.SSID);

    wifiManager.disconnect();
    wifiManager.enableNetwork(netId, true);
    wifiManager.reconnect();
}

到目前为止,在我测试过的大多数设备上,什么对我有用,有一个后备选项至少可以阻止可怕的 'looping request' 并允许成功的手动连接

下面的代码是用Kotlin写的,如果需要请google如何转换成Java

创建一个 NetworkCallback,它是 API >= 29 所必需的(之前它不是必需的,但可以使用)

val networkCallback = object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        super.onAvailable(network)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // To make sure that requests don't go over mobile data
            connectivityManager.bindProcessToNetwork(network)
        } else {
            connectivityManager.setProcessDefaultNetwork(network)
        }
    }

    override fun onLost(network: Network) {
        super.onLost(network)
        // This is to stop the looping request for OnePlus & Xiaomi models
        connectivityManager.bindProcessToNetwork(null)
        connectivityManager.unregisterNetworkCallback(networkCallback)
        // Here you can have a fallback option to show a 'Please connect manually' page with an Intent to the Wifi settings
    }
}

按如下方式连接到网络:

val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
    .setSsid(ssid)
    .setWpa2Passphrase(pass)
    .build()

val networkRequest = NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
// Add the below 2 lines if the network should have internet capabilities.
// Adding/removing other capabilities has made no known difference so far
//    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
//    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
    .setNetworkSpecifier(wifiNetworkSpecifier)
    .build()

connectivityManager.requestNetwork(networkRequest, networkCallback)

here by Google 所述,一些 OEM Rom 不是 'holding on to the request',因此连接立即断开。 OnePlus 已经在他们后来的一些型号中解决了这个问题,但不是全部。对于某些 Android 构建中的某些 phone 模型,此错误将持续存在,因此需要成功的回退(即手动连接,没有网络中断)。没有已知的解决方法可用,但如果找到我会在此处更新它作为一个选项。

要删除网络,请执行以下操作:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    //This is required for Xiaomi models for disconnecting
    connectivityManager.bindProcessToNetwork(null)
} else {
    connectivityManager.setProcessDefaultNetwork(null)
}
connectivityManager.unregisterNetworkCallback(it)

请记住,自动连接允许自动和手动断开连接。手动连接(例如建议的 OnePlus 设备回退)不允许自动断开连接。这也需要在应用程序中处理,以便在涉及物联网设备时实现更好的用户体验设计。

一些额外的小提示和信息:

  • 现在系统对话框打开,应用程序分别调用 onPause 和 onResume。这影响了我关于自动连接到物联网设备的逻辑。在某些情况下,onResume 在网络回调完成之前被调用。

  • 关于测试,我还不能通过使用浓缩咖啡来绕过对话框,它可能会阻止一些在 API 29 之前工作的测试。它可能是可能使用其他框架,如 uiautomator。在我的例子中,我调整了测试直到对话框显示,然后 运行 进一步测试。 使用 Intents.init() 不起作用。

  • onUnavailable 在找到网络但用户取消时调用。当找不到网络时不会调用它,或者如果用户在找到网络之前取消对话,在这种情况下不会调用其他方法,请使用 onResume 来捕获它。

  • 当它在 OnePlus 上失败时,它分别调用 onAvailable() -> onCapabilitiesChanged() -> onBlockedStatusChanged (blocked: false) -> onCapabilitiesChanged() -> onLost()

  • removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 不会帮助保持 OnePlus 上的连接,如所述 here

  • 如所述here

    设置 Bssid 无助于保持 OnePlus 上的连接
  • google 帮不上忙,他们说这不是他们能控制的 here

  • OnePlus 论坛帖子确认它在更新后适用于某些型号(但不是全部),请参阅 here, here & here

  • 当GPS关闭时,网络的SSID名称不可用

  • 如果对话框多次出现,请检查您自己的 activity 生命周期,在我的例子中,一些模型在收到网络回调之前调用了 onResume。

  • 手动连接到没有互联网功能的网络需要用户确认以保持连接(有时以对话框或通知的形式),如果忽略,系统将很快断开网络连接之后

测试的设备列表:

  • Google Pixel 2 - 未发现问题
  • 三星 S10 SM-G970F - 未发现问题
  • 三星 S9 SM-G960F - 未发现问题
  • One Plus A5000 (OxegenOS 10.0.1) - 自动连接的主要问题
  • HTC One M8 (LineageOS 17.1) - 未发现问题
  • 小米 Note 10 - 断开连接问题(已修复,参见代码示例)
  • Samsung A50 - 连接成功后重复出现对话框(有时)
  • Huawei Mate Pro 20 - 连接成功后重复出现对话框(有时)
  • Huawei P40 Lite - 不调用 onLost()
  • CAT S62 Pro - 未发现问题
  • 索尼 Xperia SZ2 - 未发现问题
  • 三星 Note10 - 未发现问题

从 Android 10 开始,我必须使用以下代码连接到特定的 wifi 网络。

private ConnectivityManager mConnectivityManager;

@Override
    public void onCreate(@Nullable Bundle savedInstanceState){
// instantiate the connectivity manager
        mConnectivityManager = (ConnectivityManager) this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);

}

public void connect(String ssid, String password) {
        NetworkSpecifier networkSpecifier  = new WifiNetworkSpecifier.Builder()
                  .setSsid(ssid)
                  .setWpa2Passphrase(password)
                  .setIsHiddenSsid(true) //specify if the network does not broadcast itself and OS must perform a forced scan in order to connect
                  .build();
        NetworkRequest networkRequest  = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .setNetworkSpecifier(networkSpecifier)
                .build();
        mConnectivityManager.requestNetwork(networkRequest, mNetworkCallback);
    }
    
    public void disconnectFromNetwork(){
        //Unregistering network callback instance supplied to requestNetwork call disconnects phone from the connected network
        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    }
    
    private ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager.NetworkCallback(){
            @Override
            public void onAvailable(@NonNull Network network) {
                super.onAvailable(network);
                //phone is connected to wifi network
            }
    
            @Override
            public void onLosing(@NonNull Network network, int maxMsToLive) {
                super.onLosing(network, maxMsToLive);
                //phone is about to lose connection to network
            }
    
            @Override
            public void onLost(@NonNull Network network) {
                super.onLost(network);
                //phone lost connection to network
            }
    
            @Override
            public void onUnavailable() {
                super.onUnavailable();
                //user cancelled wifi connection
            }
        };

参考资料: https://anutoshdatta.medium.com/new-wifi-apis-on-android-10-481c525108b7 https://developer.android.com/guide/topics/connectivity/wifi-suggest

我也遇到了同样的问题,3个月了也解决不了。但我找到了一个很棒的解决方案。

startActivity(new Intent("android.settings.panel.action.INTERNET_CONNECTIVITY"))

只需添加这些行而不是从应用程序连接到 wifi,这将提示用户 select wifi 并连接,一旦用户这样做,它就会连接到 wifi 并且它也会也会有互联网。

仅从应用程序连接到 wifi 将无法访问互联网。这样做,这是最好的解决方案。