【问题标题】:Java SystemTray icon does not always workJava SystemTray 图标并不总是有效
【发布时间】:2015-07-11 19:23:36
【问题描述】:

我需要您的帮助:我正在开发一个小型 Java 应用程序(Java 版本 7),它必须最小化到系统托盘中。

我使用的是 SystemTray 类,带有 SystemTray.isSupported(),然后

SystemTray systemTray = SystemTray.getSystemTray();
ImageIcon icon = new javax.swing.ImageIcon(getClass().getResource("icon.png"));

[...]

systemTray.add(trayIcon);

(当然是弹出窗口)

在 Windows 上,它运行良好。在 XFCE、Xubuntu 上,没问题,图标正在使用弹出窗口。但是在 KDE 和 Gnome shell 上......它不起作用。

KDE (4.14.1)

(Qt:4.8.6 工具 Plasma:4.11.12)

SystemTray.isSupported() = true 并且当程序到达该行时: systemTray.add(trayIcon);捕获到异常:

托盘过程中的错误: java.awt.AWTException: TrayIcon 无法显示。

因此图标是白色的,当用户点击它时不起作用,没有弹出窗口。

Gnome Shell (3.12.2)

SystemTray.isSupported() = true,图标位于底部通知区域,但鼠标事件不起作用...

为了解决这些问题,我认为 SWT 可能是一个好主意。但是当我实现它(最新版本)时,我收到了这个警告:

警告**:无法连接到无障碍总线:连接失败 到套接字 /tmp/[...]

它不起作用...... 编辑:不再,我可以用外部类解决 SWT 的问题。该警告不是由 SWT 引起的,可能是环境系统引起的(我在终端中的其他应用程序也有相同的警告)。


那么现在,我能做些什么呢? 我认为 check environment systemSystem.getenv("XDG_CURRENT_DESKTOP")System.getenv("GDMSESSION") 然后启用或禁用系统托盘,如果它是 KDE 或 Gnome 3...但这个解决方案并不是真的,因为它是本地 多平台解决方案(我的意思是操作系统的功能),而不是全局 解决方案(所有操作系统的一种方法)...

那么,其他想法?我不知道...有没有办法在系统托盘中定义一个嵌入式 JWindow?

【问题讨论】:

  • 请发布整个堆栈跟踪,而不仅仅是异常消息。完整跟踪中可能包含有用的信息。
  • 遗憾的是没有堆栈跟踪,除了糟糕的“TrayIcon 无法显示”之外没有其他信息。

标签: java system-tray kde gnome-shell


【解决方案1】:

我自己也遇到过这个问题,我记得我在用一个合法的解决方案来解决这个问题时碰到了一堵砖墙。我将问题追溯到对 TrayIcon.addNotify() 方法的调用随机失败。我似乎记得这是因为内部存在竞争条件,对 X11 系统的调用需要很长时间才能完成,因此 Java 端放弃了。

但是,如果您有一台具有不错显卡的忍者 PC,您可能永远不会遇到这种情况,这可能就是它尚未修复的原因。我的开发机器运行缓慢,所以大约 50% 的时间发生在我身上。

我一起破解一个快速而肮脏的解决方案,其中包括尝试重复调用 addNotify(每次尝试之间有一个暂停),直到它成功(或失败最多次数)。不幸的是,唯一的方法是通过反射,因为 addNotify 方法是包私有的。

代码如下:

public class HackyLinuxTrayIconInitialiser extends SwingWorker<Void, TrayIcon> {
    private static final int    MAX_ADD_ATTEMPTS    = 4;
    private static final long   ADD_ICON_DELAY      = 200;
    private static final long   ADD_FAILED_DELAY    = 1000;

    private TrayIcon[]  icons;

    public HackyLinuxTrayIconInitialiser(TrayIcon... ic) {
        icons = ic;
    }

    @Override
    protected Void doInBackground() {
        try {
            Method addNotify = TrayIcon.class.getDeclaredMethod("addNotify", (Class<?>[]) null);
            addNotify.setAccessible(true);
            for (TrayIcon icon : icons) {
                for (int attempt = 1; attempt < MAX_ADD_ATTEMPTS; attempt++) {
                    try {
                        addNotify.invoke(icon, (Object[]) null);
                        publish(icon);
                        pause(ADD_ICON_DELAY);
                        break;
                    } catch (NullPointerException | IllegalAccessException | IllegalArgumentException e) {
                        System.err.println("Failed to add icon. Giving up.");
                        e.printStackTrace();
                        break;
                    } catch (InvocationTargetException e) {
                        System.err.println("Failed to add icon, attempt " + attempt);
                        pause(ADD_FAILED_DELAY);
                    }
                }
            }
        } catch (NoSuchMethodException | SecurityException | NoSuchFieldException e1) {
            Log.err(e1);
        }
        return null;
    }

    private void pause(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e1) {
            Log.err(e1);
        }
    }

    @Override
    protected void process(List<TrayIcon> icons) {
        for (TrayIcon icon : icons) {
            try {
                tray.add(icon);
            } catch (AWTException e) {
                Log.err(e);
            }
        }
    }
}

要使用它,只需调用:

if (<OS is Linux>) {
    new HackyLinuxTrayIconInitialiser(ticon, micon, licon).execute();
} else {
    try {
        tray.add(ticon);
        tray.add(micon);
        tray.add(licon);
    } catch (AWTException e) {
        Log.err(e);
    }
}

我似乎记得当时我不能一直调用 SystemTray.add(icon),因为如果我这样做的话,这会在系统托盘上留下“幽灵”托盘图标。

希望这会有所帮助。

【讨论】: