强制 Android 在没有互联网的情况下使用 Wifi 网络

Posted

技术标签:

【中文标题】强制 Android 在没有互联网的情况下使用 Wifi 网络【英文标题】:Force Android to Use Wifi network with no internet 【发布时间】:2017-07-18 10:38:21 【问题描述】:

我正在构建一个需要通过无法访问互联网的 WiFi 网络进行通信的 android 应用程序。问题是即使连接了 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);
            
        );
    

【问题讨论】:

你可以试试我在这篇文章中给出的解决方案:***.com/questions/42329775/… barCodeData 是什么上下文? 【参考方案1】:

您可以通过将 captive_portal_detection_enabled 设置为 0 (false) 来解决此问题。

实际发生的情况是,默认情况下,每次您连接到 wifi 时,FW 都会针对服务器(通常是 google)进行测试,以查看它是否是强制 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 尝试直接使用字符串“captive_portal_detection_enabled”,而不是根据您使用的 Android 版本不可见的常量。 这需要 android.permission.WRITE_SECURE_SETTINGS 不可用。我在问题中指定轮换也不是一种选择。 ***.com/a/13045819/1685748 此 AFAIK 将仅确定是否将网络标记为 VALIDATED 以用作默认网络。除了 cmets 中提到的其他问题外,如果将不支持内部的网络设置为默认网络,这也可能导致设备失去所有连接。【参考方案2】:

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

否则常见的行为是,它将始终回退到最佳可用连接(而与互联网网关的连接可能是首选,因为它被大多数应用程序使用)。

另见answer。

【讨论】:

【参考方案3】:

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【参考方案4】:

您走在正确的道路上,解决方案确实是ConnectivityManager.bindProcessToNetwork(network)。此信息发布在本文的 Android 开发者博客上:Connecting your App to a Wi-Fi Device。

查看您的代码barCodeData.getSsid() 看起来并没有获取当前连接的 wifi 网络的 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 ""

如果仍然无法正常工作,请关注my complete solution,我使用了相同的方法,但进行了一些额外的检查。我在 Android 版本 5.1.1、6.0、6.0.1、7.1.1 和 8.1.0 中对其进行了测试。

【讨论】:

【参考方案5】:

您可以检查 wifi 是否已连接,然后继续向用户显示一个对话框,要求他连接到 wifi 网络

由于方法 NetworkInfo.isConnected() 现在在 API-23 中已被弃用,这里有一个方法可以检测 Wi-Fi 适配器是否打开并且是否还使用 WifiManager 连接到接入点:

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
    

【讨论】:

【参考方案6】:

你走在正确的道路上,你只需要稍微调整一下你所拥有的。我看到的主要问题之一是您在 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);

【讨论】:

以上是关于强制 Android 在没有互联网的情况下使用 Wifi 网络的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有互联网的情况下仅使用 GPS 在 android 中获取位置

如何在没有互联网连接的情况下在android中获取当前纬度和经度?

在没有互联网的情况下在android上获取位置坐标(纬度和经度)

在没有网络访问的本地 wifi 上强制 Android 使用 3G

在没有互联网访问的情况下运行 Android 模拟器 [关闭]

Android下强制显示ActionBar的overflowbutton