package com.lanremote.watch; import android.content.Context; import android.net.wifi.WifiManager; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.util.Collections; import java.util.List; /** * 安卓侧的自动发现包装。 * * 真正干活的是 {@link DiscoveryCore}(纯 Java,可桌面联调)。这里多做两件事: * 1. 抓 WifiManager.MulticastLock(部分手表息屏/省电时会过滤广播) * 2. 算出**本网段的定向广播地址**,这是广播探测能不能收到回复的关键 */ public final class Discovery { public static final int PORT = DiscoveryCore.PORT; private Discovery() { } /** 本机在某张网卡上的 IPv4 与前缀长度。 */ public static class LocalNet { public final String ip; public final int prefix; public final String iface; LocalNet(String ip, int prefix, String iface) { this.ip = ip; this.prefix = prefix; this.iface = iface; } public String broadcast() { return DiscoveryCore.directedBroadcast(ip, prefix); } @Override public String toString() { return iface + " " + ip + "/" + prefix; } } /** 取当前可用的局域网 IPv4(优先 wlan*)。 */ public static LocalNet localNet() { try { LocalNet fallback = null; for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) { if (!ni.isUp() || ni.isLoopback()) { continue; } String name = ni.getName() == null ? "" : ni.getName(); if (name.startsWith("p2p") || name.startsWith("dummy")) { continue; // WiFi Direct / 占位网卡 } for (InterfaceAddress ia : ni.getInterfaceAddresses()) { InetAddress addr = ia.getAddress(); if (!(addr instanceof Inet4Address) || addr.isLoopbackAddress()) { continue; } int prefix = ia.getNetworkPrefixLength(); if (prefix <= 0 || prefix > 32) { continue; } LocalNet net = new LocalNet(addr.getHostAddress(), prefix, name); if (name.startsWith("wlan")) { return net; // wlan 最优,直接返回 } if (fallback == null) { fallback = net; } } } return fallback; } catch (Exception e) { return null; } } /** 本网段定向广播地址,如 192.168.10.255。 */ public static String directedBroadcast() { LocalNet net = localNet(); return net == null ? null : net.broadcast(); } /** 广播搜索(自动带上本网段定向广播)。 */ public static List search(Context context, int timeoutMs) { WifiManager.MulticastLock lock = acquireLock(context); try { return DiscoveryCore.search(timeoutMs, directedBroadcast()); } finally { releaseLock(lock); } } /** * 子网 TCP 扫描 —— 广播被拦时的兜底。 * 注意:会阻塞,必须在后台线程调用。 */ public static List scanSubnet(Context context, int port, int connectTimeoutMs) { LocalNet net = localNet(); if (net == null || net.prefix > 30) { return new java.util.ArrayList(); } WifiManager.MulticastLock lock = acquireLock(context); try { return DiscoveryCore.scanSubnet(net.ip, net.prefix, port, connectTimeoutMs, 300); } finally { releaseLock(lock); } } /** * 请电脑打开「网络自检」。 * 返回电脑名;没收到回应返回 null。 */ public static String requestSelfCheck(Context context, int timeoutMs) { WifiManager.MulticastLock lock = acquireLock(context); try { return DiscoveryCore.requestSelfCheck(directedBroadcast(), timeoutMs); } finally { releaseLock(lock); } } public static DiscoveryCore.Found probeHost(String host, int timeoutMs) { return DiscoveryCore.probeHost(host, timeoutMs); } public static boolean ping(String host, int port, int timeoutMs) { return DiscoveryCore.ping(host, port, timeoutMs); } private static WifiManager.MulticastLock acquireLock(Context context) { try { WifiManager manager = (WifiManager) context.getApplicationContext() .getSystemService(Context.WIFI_SERVICE); if (manager == null) { return null; } WifiManager.MulticastLock lock = manager.createMulticastLock("lanremote"); lock.setReferenceCounted(false); lock.acquire(); return lock; } catch (Exception e) { return null; } } private static void releaseLock(WifiManager.MulticastLock lock) { if (lock == null) { return; } try { if (lock.isHeld()) { lock.release(); } } catch (Exception ignored) { // 释放失败不影响结果 } } }