使用JNA获取可见窗口,解决Java窗口Overlay追踪的可见性判断问题
解决方案:准确判断窗口可见性+优化Overlay更新性能
一、替代IsWindowVisible的窗口可见性判断逻辑
IsWindowVisible仅检查窗口的WS_VISIBLE标志,无法区分窗口是否被最小化、遮挡或处于非当前桌面。结合以下API可以实现更准确的判断:
1. 新增必要的JNA映射
先补充User32接口和相关结构体定义:
import com.sun.jna.Structure; import com.sun.jna.platform.win32.WinDef.*; import com.sun.jna.win32.StdCallLibrary; import java.util.Arrays; import java.util.List; public interface User32 extends StdCallLibrary { User32 INSTANCE = Native.load("user32", User32.class, W32APIOptions.DEFAULT_OPTIONS); // 原有方法 HWND FindWindow(String lpClassName, String lpWindowName); boolean GetWindowRect(HWND handle, RECT rect); boolean IsWindowVisible(HWND handle); // 新增方法和常量 boolean IsIconic(HWND hWnd); boolean GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT lpwndpl); int SW_HIDE = 0; int SW_MINIMIZE = 6; class WINDOWPLACEMENT extends Structure { public int length = size(); public int flags; public int showCmd; public POINT ptMinPosition; public POINT ptMaxPosition; public RECT rcNormalPosition; @Override protected List<String> getFieldOrder() { return Arrays.asList("length", "flags", "showCmd", "ptMinPosition", "ptMaxPosition", "rcNormalPosition"); } } class POINT extends Structure { public int x; public int y; @Override protected List<String> getFieldOrder() { return Arrays.asList("x", "y"); } } }
2. 完整的可见性判断逻辑
结合多个API实现精准判断:
public static boolean isWindowActuallyVisible(String windowName) { HWND hwnd = User32.INSTANCE.FindWindow(null, windowName); if (hwnd == null) { return false; } // 基础可见性标志检查 if (!User32.INSTANCE.IsWindowVisible(hwnd)) { return false; } // 排除最小化窗口 if (User32.INSTANCE.IsIconic(hwnd)) { return false; } // 检查窗口显示状态 User32.WINDOWPLACEMENT wp = new User32.WINDOWPLACEMENT(); if (User32.INSTANCE.GetWindowPlacement(hwnd, wp)) { return wp.showCmd != User32.SW_HIDE && wp.showCmd != User32.SW_MINIMIZE; } return false; }
二、优化Overlay更新的性能开销
1. 合理设置轮询间隔
如果采用轮询方案,避免过高频率,推荐200-500ms一次,用定时任务实现:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; ScheduledExecutorService updateExecutor = Executors.newSingleThreadScheduledExecutor(); updateExecutor.scheduleAtFixedRate(() -> { // 更新Overlay位置和可见性 updateOverlayPositionAndVisibility(); }, 0, 300, TimeUnit.MILLISECONDS);
2. 事件驱动更新(推荐)
通过监听Windows窗口消息,仅在窗口状态变化时触发更新,避免无效轮询。可以通过设置窗口钩子监听目标窗口的移动、大小变化、显示状态变更等事件:
// 为目标窗口设置消息回调(简化示例) User32.WindowProc proc = (hWnd, msg, wParam, lParam) -> { switch (msg) { case User32.WM_MOVE: case User32.WM_SIZE: case User32.WM_SHOWWINDOW: // 窗口状态变化,更新Overlay updateOverlayPositionAndVisibility(); break; } return User32.INSTANCE.CallWindowProc(oldProc, hWnd, msg, wParam, lParam); }; User32.WindowProc oldProc = User32.INSTANCE.SetWindowLongPtr(targetHwnd, User32.GWL_WNDPROC, proc);
三、修正原代码的潜在问题
原代码未判断窗口句柄有效性,容易引发崩溃,修改后:
public static RECT getWindowInformation(String windowName) { RECT rect = new RECT(); HWND hwnd = User32.INSTANCE.FindWindow(null, windowName); if (hwnd != null) { User32.INSTANCE.GetWindowRect(hwnd, rect); } return rect; }
内容的提问来源于stack exchange,提问作者Noah Fraiture
相关产品推荐
相关产品推荐

