强制安卓使用没有互联网的Wifi网络

42

我正在开发一款Android应用程序,需要在没有互联网访问的WiFi网络上进行通信。问题在于,即使WiFi已连接,当wifi网络上没有互联网连接时,Android仍选择使用蜂窝移动数据。

我已经阅读了许多关于此问题的帖子,其中很多涉及到root设备,但对于生产应用程序来说这是不可能的(root设备不是一个选择)。其他解决方案(如下面的代码)建议使用bindProcessToNetwork(),在我的Sony Z2上运行得很完美,但在其他设备上测试时则无法工作(所有设备都运行6.0.1)。

private void bindToNetwork() {
    final ConnectivityManager connectivityManager = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkRequest.Builder builder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        builder = new NetworkRequest.Builder();
        //set the transport type do WIFI
        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
        connectivityManager.requestNetwork(builder.build(), new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {


                    connectivityManager.bindProcessToNetwork(null);
                    if (barCodeData.getSsid().contains("ap-name")) {
                        connectivityManager.bindProcessToNetwork(network);
                    }

                } else {
                    //This method was deprecated in API level 23
                    ConnectivityManager.setProcessDefaultNetwork(null);
                    if (barCodeData.getSsid().contains("ap-name")) {

                        ConnectivityManager.setProcessDefaultNetwork(network);
                    }
                }

                connectivityManager.unregisterNetworkCallback(this);
            }
        });
    }
}

你可以尝试我在这篇帖子中提供的解决方案:https://dev59.com/oZ_ha4cB1Zd3GeqPwUto#46165010 - Maycon Prado
在这个上下文中,“barCodeData”是什么? - Always Learning
嗨 @Lonergan6275,能帮忙修复一下吗?我也遇到了同样的问题,好难过。 - undefined
6个回答

4
您可以通过将captive_portal_detection_enabled设置为0(false)来解决此问题。
实际上,每次连接到wifi时,默认情况下FW会针对服务器(通常是Google)进行测试,以查看它是否是需要登录的captive wifi。因此,如果您的wifi未连接到Google,则此检查将失败。此后,设备知道wifi没有互联网连接,只会停止自动连接。
将此设置设置为0将避免进行此检查。
程序化地: Settings.Global.putInt(getContentResolver(),Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED,0); 编辑:您可能需要直接使用字符串“captive_portal_detection_enabled”,而不是根据Android版本不可见的常量。

cannot resolve Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED - Lonergan6275
@Lonergan6275,尝试直接使用字符串“captive_portal_detection_enabled”,而不是取决于您正在使用的Android版本而不可见的常量。 - lax1089
4
这需要android.permission.WRITE_SECURE_SETTINGS权限,但该权限不可用。我在问题中指定了禁止root的选项。https://dev59.com/L2cs5IYBdhLWcg3wMxNF#13045819 - Lonergan6275
据我所知,这只会确定是否将网络标记为“已验证”,以用作默认网络。除了评论中提到的其他问题之外,如果将非内部可用网络设置为默认网络,这也可能导致设备失去所有连接。 - Always Learning

2

您需要在设置中禁用移动数据(不确定是否可以以编程方式完成,这可能是一个可能的选项)-或者拿出USIM卡;

否则,常见的行为是,它将始终回退到最佳可用连接(尽管具有Internet网关的连接可能更受欢迎,因为大多数应用程序使用它)。

还请参见此答案


1
你已经走在正确的道路上,只需要稍微调整一下。我看到的主要问题之一是,你在onAvailable()中注销了网络回调,这不是一个好主意,因为网络往往会在可用/不可用之间切换,这可能会导致设备出现问题。
另一件需要考虑的事情是请求一个Wi-Fi网络,并清除其他网络功能,因为根据你的网络设置,它们可能会引起问题。
以下是另一种更简单的实现方式。
final NetworkRequest requestForWifi =
  new NetworkRequest.Builder()
  .clearCapabilities() // We only care about Wi-Fi
  .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
  .build();

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

final NetworkCallback networkCallbackWifi = new NetworkCallback() {
  @Override
  void onAvailable(Network network) {
      // Once this callback triggers, a Wi-Fi network is available
      WifiInfo wifiInfo = connectivity.getNetworkCapabilities(network).TransportInfo;
      string ssid = wifiInfo.SSID.Trim(new char[] {'"', '\"' });
      if (!ssid.contains("ap-name")) {
          return;
      }
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManager.bindProcessToNetwork(network);
      } else {
          ConnectivityManager.setProcessDefaultNetwork(network);
      }   
  }
};

// Last thing is to actually request a network.
connectivityManager.requestNetwork(requestForWifi, networkCallbackWifi);

1
您走在正确的道路上,解决方案确实是使用ConnectivityManager.bindProcessToNetwork(network)。这个信息发布在Android开发者博客的这篇文章中:将您的应用连接到Wi-Fi设备
查看您的代码,barCodeData.getSsid()并没有获取当前连接的Wi-Fi网络的SSID。如果是这种情况,您的代码将永远无法成功绑定到该网络。
尝试替换您的if语句
if (barCodeData.getSsid().contains("ap-name"))

带有

if (getNetworkSsid(context).equals("ap-name"))

在Kotlin中的辅助方法,用于检索连接的WiFi网络的SSID。
private fun getNetworkSsid(context: Context?): String {
    // WiFiManager must use application context (not activity context) otherwise a memory leak can occur
    val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
    if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
        return wifiInfo.ssid.removeSurrounding("\"")
    }
    return ""
}

如果仍然无法正常工作,请参考我的完整解决方案,其中我使用了相同的方法但添加了一些额外的检查。我在Android版本5.1.1、6.0、6.0.1、7.1.1和8.1.0中进行了测试。


1

使用 Kotlin 的解决方案

class ConnectWithoutInternetTest constructor(
private val mContext: Context,
private val connectivityManager: ConnectivityManager,
private val wifiManager: WifiManager
) {

private val mWifiBroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            WifiManager.NETWORK_STATE_CHANGED_ACTION -> {
                val info = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO)
                val isConnected = info.isConnected

                val ssid: String? = normalizeAndroidWifiSsid(wifiManager.connectionInfo?.ssid)

                if (isConnected) {
                    val builder = NetworkRequest.Builder()
                    builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                    connectivityManager.registerNetworkCallback(
                        builder.build(),
                        object : ConnectivityManager.NetworkCallback() {
                            override fun onAvailable(network: Network) {
                                super.onAvailable(network)
                                val networkInfo = connectivityManager.getNetworkInfo(network)
                                val networkSsid = networkInfo.extraInfo
                                if (networkSsid == ssid) {
                                    connectivityManager.unregisterNetworkCallback(this)
                                }
                            }
                        })
                }
            }
        }
    }
}

private fun init() {
    val intentFilter = IntentFilter()
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
    mContext.registerReceiver(mWifiBroadcastReceiver, intentFilter)
}

private fun destroy() {
    mContext.unregisterReceiver(mWifiBroadcastReceiver)
}

private fun normalizeAndroidWifiSsid(ssid: String?): String? {
    return ssid?.replace("\"", "") ?: ssid
}

fun connectToWifi(ssidParam: String, password: String?) {
    init()
    val ssid = "\"$ssidParam\""
    val config = wifiManager.configuredNetworks.find { it.SSID == ssid }
    val netId = if (config != null) {
        config.networkId
    } else {
        val wifiConfig = WifiConfiguration()
        wifiConfig.SSID = ssid
        password?.let { wifiConfig.preSharedKey = "\"$password\"" }
        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
        wifiManager.addNetwork(wifiConfig)
    }

    wifiManager.disconnect()
    val successful = wifiManager.enableNetwork(netId, true)
}

如果有人尝试使用旧的API,请注意:广播接收器中的WifiManager.NETWORK_STATE_CHANGED_ACTION应更改为WifiManager.WIFI_STATE_CHANGED_ACTION。 - Vasili Fedotov

0

您可以检查wifi是否已连接,然后继续操作,否则向用户显示对话框,要求其连接到wifi网络。

由于API-23中方法NetworkInfo.isConnected()已被弃用,因此这里提供了一种使用WifiManager检测Wi-Fi适配器是否打开并连接到访问点的方法:

private boolean checkWifiOnAndConnected() {
    WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);

    if (wifiMgr.isWifiEnabled()) { // Wi-Fi adapter is ON

        WifiInfo wifiInfo = wifiMgr.getConnectionInfo();

        if( wifiInfo.getNetworkId() == -1 ){
            return false; // Not connected to an access point
        }
        return true; // Connected to an access point
    }
    else {
        return false; // Wi-Fi adapter is OFF
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接