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

Java中如何监听系统剪贴板关闭事件?规避剪贴板访问异常

解决方案:检测剪贴板关闭并避免异常

一、Java层面的优化方案(无需原生代码)

目前Java AWT没有提供直接监听剪贴板关闭的API,但可以通过异常捕获+有限重试的方式替代固定sleep,解决IllegalStateException问题,同时尽量减少UI延迟:

修改后的监听代码

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorListener;
import java.util.Arrays;
import java.util.Scanner;

public class Foo {
    private static final int MAX_RETRIES = 3;
    private static final long RETRY_INTERVAL_MS = 100;

    public static void main(String[] args) {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        FlavorListener listener = e -> {
            boolean hasText = false;
            int retryCount = 0;
            while (retryCount < MAX_RETRIES) {
                try {
                    hasText = Arrays.stream(clipboard.getAvailableDataFlavors())
                            .anyMatch(DataFlavor::isFlavorTextType);
                    break;
                } catch (IllegalStateException ex) {
                    retryCount++;
                    if (retryCount >= MAX_RETRIES) {
                        // 重试失败,可记录日志或做降级处理
                        hasText = false;
                        break;
                    }
                    try {
                        Thread.sleep(RETRY_INTERVAL_MS);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
            System.out.print("\rClipboard has text: " + hasText + "\nPress enter to exit...");
        };
        clipboard.addFlavorListener(listener);

        System.out.print("Press enter to exit...");
        new Scanner(System.in).nextLine();
    }
}

方案优势

  • 仅在捕获到异常时才重试,避免无意义的等待
  • 限制重试次数和间隔,不会长时间阻塞UI线程
  • 无需额外线程或原生代码实现,兼容性好

二、原生Windows API实现剪贴板关闭监听(进阶方案)

如果需要精准检测剪贴板被其他程序关闭的时机,可以通过JNI调用Windows原生API,利用剪贴板查看链机制实现:

核心实现思路

  1. 加入剪贴板查看链:调用SetClipboardViewer将Java程序的窗口注册为剪贴板查看器
  2. 监听剪贴板消息:处理WM_DRAWCLIPBOARD(剪贴板内容变化)和WM_CHANGECBCHAIN(查看链变更)消息,结合尝试打开剪贴板的结果,感知剪贴板被占用或释放的状态
  3. JNI回调Java方法:在检测到剪贴板状态变化(包括释放)时,通过JNI触发Java层的回调逻辑

原生代码示例(C++ DLL)

#include <windows.h>
#include <jni.h>

static HWND g_hWnd = NULL;
static jobject g_callbackObj = NULL;
static jmethodID g_callbackMethod = NULL;

// 窗口过程,处理剪贴板消息
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DRAWCLIPBOARD:
            // 剪贴板状态变更,通知Java层
            if (g_callbackObj && g_callbackMethod) {
                JNIEnv* env;
                JavaVM* jvm;
                g_callbackObj->GetEnv((void**)&env, JNI_VERSION_1_8);
                env->CallVoidMethod(g_callbackObj, g_callbackMethod);
            }
            // 传递消息给查看链中的下一个窗口
            SendMessage((HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA), uMsg, wParam, lParam);
            return 0;
        case WM_CHANGECBCHAIN:
            // 更新查看链
            if ((HWND)wParam == (HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA)) {
                SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam);
            } else {
                SendMessage((HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA), uMsg, wParam, lParam);
            }
            return 0;
        case WM_DESTROY:
            // 退出查看链
            ChangeClipboardChain(hWnd, (HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA));
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

// JNI方法:注册剪贴板监听
extern "C" JNIEXPORT void JNICALL
Java_com_example_ClipboardListener_registerClipboardListener(JNIEnv* env, jobject obj) {
    // 保存回调对象和方法
    g_callbackObj = env->NewGlobalRef(obj);
    jclass cls = env->GetObjectClass(obj);
    g_callbackMethod = env->GetMethodID(cls, "onClipboardStateChanged", "()V");

    // 创建隐藏窗口用于接收剪贴板消息
    const wchar_t CLASS_NAME[] = L"ClipboardListenerWindow";
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = CLASS_NAME;
    RegisterClass(&wc);

    g_hWnd = CreateWindowEx(0, CLASS_NAME, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
    // 加入剪贴板查看链
    SetWindowLongPtr(g_hWnd, GWLP_USERDATA, (LONG_PTR)SetClipboardViewer(g_hWnd));

    // 启动消息循环(需在单独线程运行,避免阻塞Java UI线程)
    HANDLE thread = CreateThread(NULL, 0, [](LPVOID param) -> DWORD {
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return 0;
    }, NULL, 0, NULL);
    CloseHandle(thread);
}

// JNI方法:取消监听
extern "C" JNIEXPORT void JNICALL
Java_com_example_ClipboardListener_unregisterClipboardListener(JNIEnv* env, jobject obj) {
    if (g_hWnd) {
        ChangeClipboardChain(g_hWnd, (HWND)GetWindowLongPtr(g_hWnd, GWLP_USERDATA));
        DestroyWindow(g_hWnd);
        g_hWnd = NULL;
    }
    if (g_callbackObj) {
        env->DeleteGlobalRef(g_callbackObj);
        g_callbackObj = NULL;
    }
}

Java层调用示例

public class ClipboardListener {
    static {
        System.loadLibrary("ClipboardListener");
    }

    private native void registerClipboardListener();
    private native void unregisterClipboardListener();

    public void startListening() {
        registerClipboardListener();
    }

    public void stopListening() {
        unregisterClipboardListener();
    }

    // 原生回调方法
    public void onClipboardStateChanged() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        try {
            boolean hasText = Arrays.stream(clipboard.getAvailableDataFlavors())
                    .anyMatch(DataFlavor::isFlavorTextType);
            System.out.println("Clipboard state changed, has text: " + hasText);
        } catch (IllegalStateException ex) {
            // 此时剪贴板仍被占用,可稍后重试
            System.out.println("Clipboard is still occupied");
        }
    }
}

方案优势

  • 能精准感知剪贴板的状态变化,包括被其他程序占用和释放的时机
  • 避免了Java层轮询或固定sleep的弊端
  • 完全基于Windows原生机制,可靠性高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 00:27:07