You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Java/JavaFX实现Windows关机时阻止关机的提示功能

JavaFX程序实现Windows关机拦截的落地方法

纯Java标准API无法实现系统级的关机阻止效果,网上流传的addShutdownHook方案只能在关机流程触发后拿到几秒钟的清理时间,根本拦不住关机流程,也弹不出你说的「Application x prevents shutdown」系统提示。要实现这个效果必须对接Windows原生的关机拦截机制,目前最通用的方案是用JNA直接调用Windows User32.dll的官方API,不需要写C++原生代码,打出来的jar或者exe都能正常生效。

核心实现逻辑

Windows Vista及之后版本(包括Win10/Win11)的关机拦截规则很明确:

  • 程序必须有一个可见的顶层窗口(JavaFX正常显示的Stage完全满足这个要求)
  • 调用ShutdownBlockReasonCreate接口向系统注册阻止关机的原因文本
  • 响应系统发送的WM_QUERYENDSESSION窗口消息,需要拦截时返回拒绝响应,系统就会自动弹出包含你注册的原因文本的阻止关机提示页,用户可以选择强制关机或者取消关机返回系统
  • 不需要拦截的时候调用ShutdownBlockReasonDestroy接口解除注册,系统关机就不会再被你的程序阻断

具体编码步骤

1. 引入JNA依赖

用Maven/Gradle引入JNA和JNA平台库,官方已经封装好了所有Windows API的绑定,不需要自己写原生声明:

<!-- Maven依赖示例 -->
<dependencies>
    <dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna</artifactId>
        <version>5.13.0</version>
    </dependency>
    <dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna-platform</artifactId>
        <version>5.13.0</version>
    </dependency>
</dependencies>

如果是Java 9+模块化项目,记得在module-info.java中开放JNA需要的反射权限,打包时加对应JVM参数即可。

2. 获取JavaFX窗口的原生句柄

所有Windows API都需要拿到窗口的HWND句柄才能操作,必须在stage.show()执行之后再获取,否则句柄为空会导致调用失败:

private static HWND getStageHwnd(Stage stage) {
    // Java 8 可直接用impl_getPeer获取
    long peerHandle = ((com.sun.javafx.tk.TKStage) stage.impl_getPeer()).getRawHandle();
    // Java 11+高版本如果impl_getPeer不可用,替换为下面的反射获取逻辑
    // try {
    //     Method getPeerMethod = Window.class.getDeclaredMethod("getPeer");
    //     getPeerMethod.setAccessible(true);
    //     Object tkStage = getPeerMethod.invoke(stage);
    //     Method getHandleMethod = tkStage.getClass().getDeclaredMethod("getRawHandle");
    //     getHandleMethod.setAccessible(true);
    //     long peerHandle = (long) getHandleMethod.invoke(tkStage);
    // } catch (Exception e) {
    //     throw new RuntimeException("获取窗口句柄失败", e);
    // }
    return new HWND(new Pointer(peerHandle));
}

高版本JavaFX打包如果报模块访问错误,给JVM加启动参数--add-exports javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED即可。

3. 注册关机拦截回调

在你需要阻止关机的场景(比如用户有未保存的文档、正在进行不能中断的文件导出等),先注册阻止原因,再挂载窗口消息处理回调:

// 拿到主窗口句柄
HWND mainHwnd = getStageHwnd(primaryStage);
// 注册阻止原因,这个文本会显示在系统的阻止关机提示页
String blockReason = "当前有未保存的编辑内容,关机将导致数据丢失";
if (!User32.INSTANCE.ShutdownBlockReasonCreate(mainHwnd, blockReason.toCharArray())) {
    // 注册失败一般是句柄无效、窗口被最小化到托盘无可见界面导致
    System.err.println("注册关机阻止失败,错误码:" + Native.getLastError());
}

// 挂载窗口消息回调,响应系统的关机询问
User32.INSTANCE.SetWindowLongPtr(mainHwnd, User32.GWL_WNDPROC, new WindowProc() {
    @Override
    public LRESULT callback(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam) {
        // 收到系统关机询问消息
        if (msg == WinUser.WM_QUERYENDSESSION) {
            // 自定义判断逻辑:有未保存内容就拦截,否则放行
            if (checkHasUnsavedWork()) {
                // 返回0表示拒绝关机,触发系统阻止提示
                return new LRESULT(0);
            }
            // 放行关机,提前销毁阻止标记
            User32.INSTANCE.ShutdownBlockReasonDestroy(mainHwnd);
            return new LRESULT(1);
        }
        // 其他消息交给系统默认处理
        return User32.INSTANCE.DefWindowProc(hWnd, msg, wParam, lParam);
    }
});

当用户完成保存、中断的任务执行结束后,主动调用User32.INSTANCE.ShutdownBlockReasonDestroy(mainHwnd)解除拦截即可,避免无意义阻断用户关机。

避坑提示

  • 不要无理由全程拦截关机,按照微软的设计规范,只有存在用户未保存的状态数据时才允许调用该接口,否则会被系统安全组件判定为恶意程序。
  • 如果程序最小化到系统托盘,要保证有一个隐藏的顶层窗口存在,否则API调用会失效。
  • 不要把程序注册为Windows服务,服务运行在会话0隔离层,没有交互桌面窗口,无法弹出用户可见的阻止提示。
  • 用jpackage、exe4j等工具打exe包时不需要额外配置,只要JNA的依赖正常打包进去就能生效。

内容的提问来源于stack exchange,提问作者yck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 08:51:32