Android 12 以太网/Wi-Fi 双网绑定后无法发现 BACnet 设备
2025-03-20 03:27:28
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 及更高版本中,当多个网络接口同时激活时,系统可能会更严格地执行网络绑定和路由规则。
结合,我猜可能有以下几个原因:
- 路由问题: 即使应用进程绑定到了以太网,BACnet 发现报文 (通常是 UDP 广播或多播) 可能仍然通过默认路由 (可能是 Wi-Fi) 发送,导致无法到达以太网上的 BACnet 设备。
- 多播/广播限制: Android 12 可能会对多播和广播流量实施更严格的限制, 特别是在同时连接多个网络时,防止跨网络接口泄漏。
bindProcessToNetwork
的行为变更: 虽然bindProcessToNetwork
在日志中显示成功, 但在 Android 12 上,它可能并不像在旧版本 Android 上那样完全控制所有网络流量。有可能部分请求还是从WiFi走了。- 防火墙或安全策略: 设备的防火墙或其他安全策略可能阻止了跨接口的 BACnet 发现流量。
- 网络服务发现 (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 和以太网的优先级
在某些情况下,可以尝试调整网络接口的优先级,将以太网设置为更高优先级。这通常需要在设备设置中手动进行。
原理: 系统通常会优先使用优先级较高的网络接口。 这样做可以让系统更倾向于选择以太网。
操作步骤:
- 进入设备的“设置”。
- 找到“网络和互联网”或类似选项。
- 找到高级网络设置或网络优先级设置。
- 将以太网的优先级设置为高于 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 和子网掩码配置可以避免网络冲突,确保不同网络接口上的设备可以正确通信。
操作步骤:
- 分别检查以太网和 Wi-Fi 的 IP 地址、子网掩码和网关。
- 确保它们不在同一个子网。 例如, 以太网可以使用
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) 进行加密。
希望能帮你定位并解决问题!