返回

Android 12 以太网/Wi-Fi 双网绑定后无法发现 BACnet 设备

Android

Android 应用绑定特定网络接口 (以太网/Wi-Fi) 后无法发现 BACnet 设备

最近在做一个 Android 应用,需要用以太网去发现本地网络上的 BACnet 设备,同时 Wi-Fi 用于正常的互联网连接。 设备同时连接了以太网和 Wi-Fi。

问题来了: 当我把应用的进程绑定到特定的网络接口(以太网或 Wi-Fi)后,虽然日志显示绑定成功了,但在以太网和 Wi-Fi 同时激活的情况下,无法通过以太网发现 BACnet 设备。这个问题出现在 Android 12(API level 31)上。相同的代码在旧版本 Android(例如 Android 7)上能正常工作,我可以成功地使用任一接口发现设备。

一、 问题原因分析

Android 的网络管理策略在不同版本间可能存在差异。 在 Android 12 及更高版本中,当多个网络接口同时激活时,系统可能会更严格地执行网络绑定和路由规则。

结合,我猜可能有以下几个原因:

  1. 路由问题: 即使应用进程绑定到了以太网,BACnet 发现报文 (通常是 UDP 广播或多播) 可能仍然通过默认路由 (可能是 Wi-Fi) 发送,导致无法到达以太网上的 BACnet 设备。
  2. 多播/广播限制: Android 12 可能会对多播和广播流量实施更严格的限制, 特别是在同时连接多个网络时,防止跨网络接口泄漏。
  3. bindProcessToNetwork 的行为变更: 虽然 bindProcessToNetwork 在日志中显示成功, 但在 Android 12 上,它可能并不像在旧版本 Android 上那样完全控制所有网络流量。有可能部分请求还是从WiFi走了。
  4. 防火墙或安全策略: 设备的防火墙或其他安全策略可能阻止了跨接口的 BACnet 发现流量。
  5. 网络服务发现 (NSD) 冲突: 如果有其他服务 (例如 mDNS) 也在使用网络,可能存在冲突。

二、解决方案

下面列出几种可行的解决方案,你可以试试:

1. 显式指定 Socket 的网络接口

比起直接绑定整个进程,更稳妥的方式是直接绑定用于 BACnet 通信的 DatagramSocket 到以太网。这样可以更精细地控制 BACnet 流量。

原理: 通过 Network.bindSocket() 方法,我们可以确保用于 BACnet 通信的 Socket 仅通过我们指定的网络接口 (以太网) 进行通信。

代码示例:

private void createAndBindBacnetSocket(Network ethernetNetwork) {
    try {
        DatagramSocket bacnetSocket = new DatagramSocket(null); // 创建时不绑定任何地址
        bacnetSocket.setReuseAddress(true); // 允许重用地址/端口
         bacnetSocket.setBroadcast(true); //开启广播
        bacnetSocket.bind(new InetSocketAddress(47808)); // 绑定到 BACnet 端口 47808, 不指定IP,让系统选。

        ethernetNetwork.bindSocket(bacnetSocket); // 绑定 Socket 到以太网

        // 现在 bacnetSocket 只会通过以太网发送和接收数据
         Log.d(TAG,"Binding socket to Ethernet network");
        // ... 使用 bacnetSocket 进行 BACnet 通信 ...
    } catch (SocketException e) {
        e.printStackTrace();
        // 处理异常
    }
}

bindToBestNetwork() 找到ethernetNetwork 后调用。不要在获取socket前关闭Wifi, 会导致网络环境变化,获取的以太网失效。

2. 使用 Network Callback 监听网络变化

使用 ConnectivityManager.NetworkCallback 监听网络变化, 并在以太网可用时重新绑定 Socket。

原理: Android 系统提供了网络状态变化的监听机制。我们可以通过注册 NetworkCallback 来实时监测网络变化, 并在以太网可用时执行绑定操作,在网络丢失的时候释放资源。

代码示例 (已在原代码中实现):

原代码已经实现了, 请看registerEthernetListener()registerWifiListener() 部分的代码. 注意需要针对你的需求, 正确处理onAvailable, onLost, onCapabilitiesChanged 等回调方法.

3. 调整 Wi-Fi 和以太网的优先级

在某些情况下,可以尝试调整网络接口的优先级,将以太网设置为更高优先级。这通常需要在设备设置中手动进行。

原理: 系统通常会优先使用优先级较高的网络接口。 这样做可以让系统更倾向于选择以太网。

操作步骤:

  1. 进入设备的“设置”。
  2. 找到“网络和互联网”或类似选项。
  3. 找到高级网络设置或网络优先级设置。
  4. 将以太网的优先级设置为高于 Wi-Fi。

注意: 并非所有 Android 设备都允许用户更改网络优先级。

4. 关闭Wi-Fi的多播锁

如果Wi-Fi开启了多播锁,可能会对以太网的多播有影响,所以可以先不获取多播锁,测试下是否有效。

将这部分代码

WifiManager.MulticastLock multicastLock = wifiManager.createMulticastLock("bacnetLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();

修改为:

//                WifiManager.MulticastLock multicastLock = wifiManager.createMulticastLock("bacnetLock");
//                multicastLock.setReferenceCounted(true);
//                multicastLock.acquire();

5. 使用ConnectivityManager.requestNetwork() (进阶)

如果 bindProcessToNetwork() 不能满足要求,可利用requestNetwork 请求特定网络, 而不是直接绑定。

原理: requestNetwork 方法允许你指定所需的网络能力, 让系统选择符合要求的网络. 它提供了更强的控制和灵活性。

代码示例:


    private Network mEthernetNetwork;
    private ConnectivityManager.NetworkCallback mNetworkCallback;

   private void requestEthernetNetwork() {
    NetworkRequest request = new NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) //不通过VPN
             .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)//不需要访问互联网
            .build();

      mNetworkCallback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            Log.d("Network", "Ethernet network available");
           mEthernetNetwork = network;
            createAndBindBacnetSocket(mEthernetNetwork);  //方法来自解决方案1
        }

        @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities)
         {
           if (!networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
                {
                     //网络已经不是以太网了,需要处理
                       mEthernetNetwork = null;
                  }

          }

        @Override
        public void onLost(Network network) {
          Log.d("Network", "Ethernet network lost");
            mEthernetNetwork = null;
        }
    };

     ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    cm.requestNetwork(request, mNetworkCallback);
}

  //在适当的时候(例如,应用退出时)取消网络请求
   private void unregisterNetworkCallback() {
    if (mNetworkCallback != null) {
          ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
         cm.unregisterNetworkCallback(mNetworkCallback);
         mNetworkCallback = null;
        }
 }

这里移除了对互联网链接的要求.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET),BACnet通常是本地网络协议,不需要互联网访问权限。
同时,.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)确保不是一个 VPN 链接。

6. 检查 IP 和子网掩码配置

确保以太网和 Wi-Fi 使用不同的 IP 地址段和子网掩码。如果它们在同一子网内,可能导致路由冲突。

原理: 正确的 IP 和子网掩码配置可以避免网络冲突,确保不同网络接口上的设备可以正确通信。

操作步骤:

  1. 分别检查以太网和 Wi-Fi 的 IP 地址、子网掩码和网关。
  2. 确保它们不在同一个子网。 例如, 以太网可以使用 192.168.1.x/24, Wi-Fi 使用 192.168.2.x/24

7.暂时禁用Wi-Fi(用于测试)

这是一个排除问题的简单方法. 在开发过程中, 临时禁用 Wi-Fi 来确认问题是否与双网络接口共存有关。如果禁用Wi-Fi后,以太网发现功能正常, 则可以将问题范围缩小到多网络环境的处理上.

8. 关闭其他的网络发现

如果您除了使用BACnet外, 还有开启mDNS或者其他基于UDP的网络服务,尝试关闭,排除干扰。

三、安全建议

  • 最小权限原则: 确保你的应用只请求必要的网络权限。
  • 谨慎处理用户数据: 如果应用需要通过 Wi-Fi 传输用户数据, 请确保使用安全协议 (如 HTTPS) 进行加密。

希望能帮你定位并解决问题!