Java 系统托盘图标并不总是有效

8
我希望你能帮助我:我正在开发一个小型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: 无法显示托盘图标。

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

Gnome Shell(3.12.2)

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

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

警告**: 无法连接到辅助功能总线:无法连接到套接字/tmp/[...]

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


那么现在我该怎么办? 我想使用System.getenv("XDG_CURRENT_DESKTOP")System.getenv("GDMSESSION")检查环境系统,然后根据KDE或Gnome 3启用或禁用系统托盘...但这个解决方案并不是真正的,因为它是一个本地解决方案,适用于多平台(我的意思是根据操作系统),而不是全局解决方案(一个方法适用于所有操作系统)...

那么,还有其他想法吗?我不知道...是否有一种方法将嵌入式JWindow定义到系统托盘中?


请发布整个堆栈跟踪,而不仅仅是异常消息。完整跟踪中可能会有有用的信息。 - dimo414
很不幸,没有堆栈跟踪信息,除了一个简单的“无法显示托盘图标”的错误信息外,没有任何其他信息。 - Drimux
1个回答

3
我曾经遇到过这个问题,我记得我尝试了多种方法来解决它,但最终都失败了。我发现问题出在 TrayIcon.addNotify() 方法上,这个方法会随机地失败。我似乎记得是由于内部的竞态条件导致了 X11 系统调用花费太长时间而 Java 侧放弃了调用。
如果您的电脑配置足够高,有一个好的显卡,您可能永远不会遇到这种情况,这也许是为什么它还没有被修复的原因。我的开发机器比较慢,所以它大约会发生一半的时间。
我采取了一个快速而简单的解决方案,即反复尝试调用 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),否则会在系统托盘上留下"幽灵"托盘图标。
希望这有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接