Android 中如何可靠区分 Hotspot 与 WiFi 的网络接口名?
结论
不能把 wlan0、wlan1、ap0 或 192.168.43.x 作为跨设备的判断规则。普通应用也不能指望通过一个公开的 NDK 调用,直接为所有本机接口标注 WiFi 客户端或 Hotspot 角色。
对于双机对战场景,可以分别处理客户端和服务器端:
- 客户端:通过 Android SDK 获取连接热点的 WiFi
Network,再读取对应的接口名,或直接将游戏 socket 绑定到该网络。 - 服务器端:根据应用创建房间、启动热点的流程确定角色。如果只需接受客户端连接,通常可以监听通配地址,减少对热点接口名的依赖。
NDK 的 android_setsocknetwork() 可以将 socket 绑定到已知网络,但不能识别接口的热点角色。Android NDK Networking 文档
为什么枚举接口还不够
问题中已经能用 C/C++ 取得接口名和地址,缺少的是接口的用途信息。
接口名、IP 地址和接口角色之间没有固定的一一对应关系。即使某台设备始终使用 wlan0 连接 WiFi,也不能据此推断其他设备的命名。192.168.43.x 同样只是特定环境中的观察结果。
客户端可以通过 Android SDK 的 NetworkCapabilities 判断网络传输类型,再用 LinkProperties 取得该网络的链路信息,而不必从接口名称猜测用途。Android 网络状态说明
客户端连接的 WiFi 网络和服务器本机提供的 Soft AP 下行接口是两个不同的查询对象。识别出客户端的 WiFi Network,并不意味着找到了通用的服务器热点接口识别方法。
客户端:从 WiFi Network 获取接口名
先声明网络访问和网络状态权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
下面的 Java 片段用于观察应用可见的 WiFi 网络。示例使用了 getNetworkHandle(),要求 API level 23 或更高。
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.util.Log;
// context 为有效的 Context。
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
ConnectivityManager.NetworkCallback callback =
new ConnectivityManager.NetworkCallback() {
@Override
public void onLinkPropertiesChanged(
Network network, LinkProperties properties) {
String interfaceName = properties.getInterfaceName();
long networkHandle = network.getNetworkHandle();
Log.d("GameNetwork", "interface=" + interfaceName
+ ", handle=" + networkHandle);
// 选中游戏所用的 Network 后,可通过 JNI 将 handle
// 传给 native 层;不要仅凭回调顺序选择网络。
}
@Override
public void onLost(Network network) {
// 清理该 Network 对应的连接状态,等待重新连接。
Log.d("GameNetwork", "lost=" + network);
}
};
cm.registerNetworkCallback(request, callback);
LinkProperties.getInterfaceName() 返回对应链路的接口名,也可能返回 null,调用方需要处理这种情况。LinkProperties API 文档
使用时还要注意:
- 这个回调只观察匹配的网络,不会主动连接热点。如果通过
WifiNetworkSpecifier发起连接,应优先使用该连接请求回调返回的Network。 - 局域网对战不需要网络具备互联网能力,因此不要额外要求
NET_CAPABILITY_INTERNET或NET_CAPABILITY_VALIDATED。 - 默认网络可能是蜂窝网络,不能直接把
getActiveNetwork()的结果当成游戏 WiFi。 - 不再观察时,调用
cm.unregisterNetworkCallback(callback)。
C/C++:直接将 socket 绑定到选中的网络
如果获取接口名只是为了让客户端流量走正确的 WiFi,可以直接使用网络句柄。
Java 层通过 Network.getNetworkHandle() 取得句柄,再通过 JNI 传给 C/C++。该句柄可用于 NDK 网络 API;它不是接口索引,也不是从 Network.toString() 解析出来的数字。Network API 文档
#include <android/multinetwork.h>
// network 来自 Java Network.getNetworkHandle()。
// fd 是已经创建、尚未 connect() 的 socket。
int BindGameSocket(net_handle_t network, int fd) {
return android_setsocknetwork(network, fd);
}
android_setsocknetwork() 从 API level 23 开始提供。返回 0 表示成功;返回 -1 时应读取 errno 并处理失败,不能假定绑定已生效。如果还需要按该网络解析域名,可以使用 android_getaddrinfofornetwork()。NDK 网络绑定与域名解析 API
网络断开后,应清理旧连接,在获得新 Network 后重新建立连接。不要长期缓存句柄并假设它始终有效。
服务器端:先判断是否真的需要热点接口名
游戏服务器通常需要接受连接,可以不依赖 AP 接口名。
对于 IPv4 TCP 服务,可以将监听地址绑定为 INADDR_ANY,再调用 listen()。这样会监听本机可用的 IPv4 地址,包括热点地址,也会接受其他可达接口上的连接。这里的 0.0.0.0 是绑定地址,不能作为服务器地址发送给客户端。
客户端仍需通过应用层服务发现或配对流程获得服务器地址和端口,不要将服务器地址固定为 192.168.43.1。某些网络中,默认网关恰好是热点主机,但这需要结合实际路由和连接验证,不能保证适用于所有热点模式。
如果业务确实要求只监听热点接口,例如需要限定多播接口或隔离其他网络,绑定通配地址就无法满足要求。此时需要针对目标 Android 版本、设备实现和应用权限,确认是否有可访问的热点或 tethering 接口信息。无法确认时,应明确兼容范围,避免通过猜测接口名称来处理。
仅用于双机通信时,可考虑 LocalOnlyHotspot
如果热点由游戏创建,且不需要共享互联网,可以考虑 WifiManager.startLocalOnlyHotspot()。它从 API level 26 开始提供,适用于附近设备的本地通信。启动成功后,应用通过回调得到 LocalOnlyHotspotReservation。它提供热点连接配置和生命周期管理,不会因此返回热点接口名。WifiManager API 文档
使用这一 API 需要相应权限,包括 CHANGE_WIFI_STATE;附近 WiFi 或位置权限的要求取决于目标版本。面向 Android 13 或更高版本的应用需要处理 NEARBY_WIFI_DEVICES。结束房间时应释放 reservation,并处理热点被用户关闭的情况。LocalOnlyHotspot 使用说明
对于原问题提到的 netlink 限制,也不宜笼统认定“Android 11+ 全部不可用”。具体可用性取决于访问的信息、系统版本和权限。这个游戏场景可以优先通过 SDK 选择客户端网络,再由 NDK 完成 socket 通信,无须将接口命名规则作为连接前提。
备注:内容仅供参考。
