如何在现代Android设备上程序化识别本地热点接口并获取相关信息?
我需要扫描自己手机热点的连接设备,以此仅和这些设备通信,核心需要广播地址,连接设备的IP地址这类关联信息也可以。
在我的设备上操作很简单:热点对应的NetworkInterface固定是wlan1(主WiFi接口是wlan0),很容易获取IP相关信息。但其他设备的热点接口名称可能变化,比如ap0/ap1;更麻烦的是,有些机型在WiFi和热点同时开启时,接口是wlan0(WiFi)和wlan1(热点),但关闭WiFi后,热点接口会变成wlan0!
我目前的代码如下:
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { String ipadd = inetAddress.getHostAddress(); if (name.equals("wlan0") || name.equals("wlan1")) { System.out.println("Found WLAN= " + name); System.out.println("IP Address = " + ipadd); for (InterfaceAddress interfaceAddress : intf.getInterfaceAddresses()) { if (interfaceAddress != null) { InetAddress broadcastadd = interfaceAddress.getBroadcast(); if (broadcastadd != null) { found_bcast_address = broadcastadd.toString(); found_bcast_address = found_bcast_address.substring(1); System.out.println("Broadcast Address = " + found_bcast_address); } } } } } }
现在的核心问题是无法确定哪个接口对应热点,导致发送广播时无法区分响应设备是连我热点的,还是我手机连的WiFi里的设备。
之前查到的两种方案在现代Android里都失效了:
- 读取
/proc/net/arp文件:现代Android系统不可行 - 反射调用
getWifiApConfiguration:该方法在新版本Android中已无法使用
请问在近期的Android系统中,有没有可行的方法获取这些信息?
针对Android 10及以上版本,可以通过以下两种可靠方式区分热点接口并获取所需信息:
1. 利用NetworkCapabilities判断热点接口
通过ConnectivityManager获取所有活跃网络,再检查网络的NetworkCapabilities特征,结合私网IP段确认热点接口:
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); for (Network network : cm.getAllNetworks()) { NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); if (capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { LinkProperties linkProps = cm.getLinkProperties(network); if (linkProps != null) { String ifaceName = linkProps.getInterfaceName(); for (LinkAddress addr : linkProps.getLinkAddresses()) { InetAddress inetAddr = addr.getAddress(); if (inetAddr instanceof Inet4Address && !inetAddr.isLoopbackAddress()) { String ip = inetAddr.getHostAddress(); // 根据子网掩码计算广播地址 int prefixLength = addr.getPrefixLength(); byte[] ipBytes = inetAddr.getAddress(); byte[] mask = new byte[4]; int bits = 0xFFFFFFFF << (32 - prefixLength); mask[0] = (byte) ((bits >> 24) & 0xFF); mask[1] = (byte) ((bits >> 16) & 0xFF); mask[2] = (byte) ((bits >> 8) & 0xFF); mask[3] = (byte) (bits & 0xFF); byte[] broadcastBytes = new byte[4]; for (int i = 0; i < 4; i++) { broadcastBytes[i] = (byte) (ipBytes[i] | (~mask[i] & 0xFF)); } InetAddress broadcastAddr = InetAddress.getByAddress(broadcastBytes); System.out.println("热点接口: " + ifaceName); System.out.println("IP地址: " + ip); System.out.println("广播地址: " + broadcastAddr.getHostAddress()); } } } } }
注意:需添加权限android.permission.ACCESS_NETWORK_STATE,Android 12及以上还需android.permission.ACCESS_WIFI_STATE。
2. 通过WifiManager获取热点状态及IP信息
结合热点开启状态,利用DhcpInfo获取网关和子网掩码,推导广播地址并匹配对应接口:
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (wifiManager.isWifiApEnabled()) { DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); if (dhcpInfo != null) { // 转换热点网关(手机自身热点IP)为字符串格式 int ipInt = dhcpInfo.serverAddress; String ip = String.format("%d.%d.%d.%d", (ipInt & 0xff), (ipInt >> 8 & 0xff), (ipInt >> 16 & 0xff), (ipInt >> 24 & 0xff)); // 转换子网掩码为字符串格式 int maskInt = dhcpInfo.netmask; String mask = String.format("%d.%d.%d.%d", (maskInt & 0xff), (maskInt >> 8 & 0xff), (maskInt >> 16 & 0xff), (maskInt >> 24 & 0xff)); // 计算广播地址 byte[] ipBytes = InetAddress.getByName(ip).getAddress(); byte[] maskBytes = InetAddress.getByName(mask).getAddress(); byte[] broadcastBytes = new byte[4]; for (int i = 0; i < 4; i++) { broadcastBytes[i] = (byte) (ipBytes[i] | (~maskBytes[i] & 0xFF)); } String broadcastAddr = InetAddress.getByAddress(broadcastBytes).getHostAddress(); System.out.println("热点IP: " + ip); System.out.println("广播地址: " + broadcastAddr); // 遍历NetworkInterface找到对应接口 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface intf = interfaces.nextElement(); Enumeration<InetAddress> addrs = intf.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress addr = addrs.nextElement(); if (addr.getHostAddress().equals(ip)) { System.out.println("热点接口名称: " + intf.getName()); break; } } } } }
注意:Android 10及以上需要android.permission.ACCESS_FINE_LOCATION权限,需完成动态权限申请流程。
扫描热点连接设备
获取广播地址后,可通过发送UDP广播包到该地址并监听响应,来发现已连接的设备;部分机型也可通过反射读取系统ARP缓存,但UDP广播的兼容性更优。
内容的提问来源于stack exchange,提问作者Marcello Carrozzino

