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() { List all = localNets(); return all.isEmpty() ? null : all.get(0); } /** * 取**所有**可用的局域网 IPv4,wlan 排在最前面。 * *

为什么不能只取一个:手表可能同时挂着 WiFi 和热点/蓝牙网络, * 一些手表还会多出一张厂商虚拟网卡。原来 {@code localNet()} 拿到第一个 * wlan 就 return 了,于是: * * 广播只发到那一个网段,另一个网段上的电脑永远搜不到 * * 子网扫描也只扫那一个网段 * 用户看到的就是"同一个 WiFi 却搜不到"。 */ public static List localNets() { List wlan = new java.util.ArrayList(); List others = new java.util.ArrayList(); try { 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; } String host = addr.getHostAddress(); if (host == null || host.startsWith("169.254.")) { continue; // APIPA 链路地址没用 } LocalNet net = new LocalNet(host, prefix, name); if (name.startsWith("wlan")) { wlan.add(net); } else { others.add(net); } } } } catch (Exception e) { // 权限不足 / 网卡枚举失败:返回已经拿到的 } List all = new java.util.ArrayList(); all.addAll(wlan); all.addAll(others); return all; } /** 本机在局域网里的 IPv4 列表(去重)。 */ public static List localIpv4() { List out = new java.util.ArrayList(); for (LocalNet net : localNets()) { if (!out.contains(net.ip)) { out.add(net.ip); } } return out; } /** 本网段定向广播地址,如 192.168.10.255。 */ public static String directedBroadcast() { LocalNet net = localNet(); return net == null ? null : net.broadcast(); } /** **所有**网段的定向广播地址(多网卡时都要探)。 */ public static List directedBroadcasts() { List out = new java.util.ArrayList(); for (LocalNet net : localNets()) { String b = net.broadcast(); if (b != null && !b.equals(net.ip) && !out.contains(b)) { out.add(b); } } return out; } /** 广播搜索(自动带上**所有**本网段定向广播 + 重发)。 */ public static List search(Context context, int timeoutMs) { WifiManager.MulticastLock lock = acquireLock(context); try { return DiscoveryCore.searchMany(timeoutMs, directedBroadcasts()); } finally { releaseLock(lock); } } /** * 子网 TCP 扫描 —— 广播被拦时的兜底。 * 注意:会阻塞,必须在后台线程调用。 */ public static List scanSubnet(Context context, int port, int connectTimeoutMs) { WifiManager.MulticastLock lock = acquireLock(context); try { List open = new java.util.ArrayList(); // 每个网段都扫一遍;/30 及更小的网段没有可用主机,跳过。 for (LocalNet net : localNets()) { if (net.prefix > 30) { continue; } for (String host : DiscoveryCore.scanSubnet(net.ip, net.prefix, port, connectTimeoutMs, 300)) { if (!open.contains(host)) { open.add(host); } } } return open; } 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) { // 释放失败不影响结果 } } }