解决Java程序的托盘图标在Windows任务栏重建后丢失的问题(使用SystemTray类创建托盘图标)

JavaSE 1.6提供了java.awt.SystemTray类用于方便地创建托盘图标.但在Windows平台下当explorer崩溃时托盘图标会丢失.
如果是本地代码或.Net平台的程序则可以很简单地获取TaskbarCreated消息并重新添加托盘图标.
但在Java程序中无法直接访问Windows消息.

解决方法是通过JNI调用本地代码安装消息钩子捕获TaskbarCreated消息并通知主程序重建图标.

Java部分

package idv.takamachi660.platform.win;

import java.util.ArrayList;
import java.util.List;

/**
 * 任务栏重建监视器
 * 
 * @author Takamachi660
 * 
 */
public class TaskBarMonitor {
    private static TaskBarMonitor instance;

    private boolean enabled;
    private long lastMsg;
    private int minimumInterval = 1500;
    private final List<TaskBarListener> listeners = new ArrayList<TaskBarListener>();

    static {
        try {
            System.loadLibrary("TaskBarMonitor");
            // 如果本地库成功加载则创建实例
            instance = new TaskBarMonitor();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 查询监视器是否可用
     */
    public static boolean isSupported() {
        return instance != null;
    }

    /**
     * 返回全局唯一实例,若不可用则返回null
     */
    public static TaskBarMonitor getInstance() {
        return instance;
    }

    private TaskBarMonitor() {
    }

    /**
     * 本地方法,安装钩子,保存必要的全局引用
     */
    private native boolean installHook();

    /**
     * 本地方法,撤销钩子,释放全局引用
     */
    private native boolean unInstallHook();

    /**
     * 从钩子处理函数调用
     */
    @SuppressWarnings("unused")
    private void hookCallback() {
        long current = System.currentTimeMillis();
        if (current - this.lastMsg >= this.getMinimumInterval())
            for (TaskBarListener l : listeners)
                l.taskBarCreated();
        this.lastMsg = current;
    }

    /**
     * 查询监视器状态(钩子是否已经安装)
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * 设置监视器状态(安装或撤销钩子),若与当前状态相同则不执行任何操作
     */
    public void setEnable(boolean enable) {
        if (this.isEnabled() != enable) {
            if (enable)
                this.enabled = this.installHook();
            else
                this.enabled = !this.unInstallHook();
        }
    }

    /**
     * 设置最小消息触发间隔(防止重复)
     */
    public void setMinimumInterval(int minimumInterval) {
        this.minimumInterval = minimumInterval;
    }

    /**
     * 获取最小消息触发间隔
     */
    public int getMinimumInterval() {
        return minimumInterval;
    }

    public void addTaskBarListener(TaskBarListener listener) {
        listeners.add(listener);
    }

    public void removeTaskBarListener(TaskBarListener listener) {
        listeners.remove(listener);
    }

    @Override
    protected void finalize() throws Throwable {
        this.setEnable(false);
        // 防止忘记调用setEnable(false)造成内存泄漏
        // 但不可靠
    }


本地部分

 

#include "idv_takamachi660_platform_win_TaskBarMonitor.h"
#include <windows.h>
#include <tchar.h>

//全局变量
HINSTANCE hInstance;//保存DLL实例句柄
HHOOK hHook;//钩子句柄
int WM_TASKBARCREATED;//TaskbarCreated消息代码
jobject jobj;//回调目标的Java对象
JavaVM *jvm;//Java虚拟机指针
jint jniVersion;//JNI版本
jmethodID jCallbackMid;//回调方法指针

LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{//钩子处理函数,判断消息是否为TaskbarCreated,若是则回调Java方法
    if(jvm)
    {
        CWPSTRUCT* pMsg=(CWPSTRUCT*)lParam;
        if(pMsg->message==WM_TASKBARCREATED)
        {
            JNIEnv *env;//获取当前线程(应该会是AWT消息循环线程)的JNIEnv
            if(JNI_OK==jvm->GetEnv((void **)&env,jniVersion))                    
                env->CallVoidMethod(jobj,jCallbackMid);//回调Java方法
        }
    }
    return CallNextHookEx(hHook,nCode,wParam,lParam);
}

JNIEXPORT jboolean JNICALL Java_idv_takamachi660_platform_win_TaskBarMonitor_installHook
(JNIEnv *env, jobject obj)
{//安装钩子,保存必要的全局引用
    WM_TASKBARCREATED=RegisterWindowMessage(_T("TaskbarCreated"));
    hHook=SetWindowsHookEx(WH_CALLWNDPROC,HookProc,hInstance,0);
    if(hHook && WM_TASKBARCREATED)
    {            
        jclass cls = env->GetObjectClass(obj);//获取监视器的Java类
        if(cls)
        {
            jmethodID tempMid=env->GetMethodID(cls,"hookCallback","()V");//获取回调方法的局部引用
            if(tempMid)
            {
                jniVersion=env->GetVersion();//获取JNI版本号
                env->GetJavaVM(&jvm);//保存JVM指针
                jobj=env->NewGlobalRef(obj);//创建监视器对象的全局引用
                jCallbackMid=(jmethodID)env->NewGlobalRef((jobject)tempMid);
                //创建回调方法的全局引用
                return (jboolean)1;
            }
        }
    }
    return (jboolean)0;
}

JNIEXPORT jboolean JNICALL Java_idv_takamachi660_platform_win_TaskBarMonitor_unInstallHook
(JNIEnv *env, jobject obj)
{//撤销钩子,释放全局引用
    if(hHook)
    {
        if(jobj)
            env->DeleteGlobalRef(jobj);
        if(jCallbackMid)
            env->DeleteGlobalRef((jobject)jCallbackMid);
        return (jboolean)UnhookWindowsHookEx(hHook);
    }
    else
        return (jboolean)0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{//DLL入口函数,保存DLL实例句柄
    if(fdwReason==DLL_PROCESS_ATTACH)
    {
        hInstance=hinstDLL;
    }
    return true;

测试


Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->import idv.takamachi660.platform.win.TaskBarListener;
import idv.takamachi660.platform.win.TaskBarMonitor;

import java.awt.AWTException;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TestTaskBarMonitor extends JFrame {
    private static final long serialVersionUID = 6600311070370645769L;
    private Image iconImg;
    private TrayIcon trayIcon;

    public TestTaskBarMonitor() {
        super("TestTaskBarMonitor");
        this.setBounds(0, 0, 160, 90);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        if (SystemTray.isSupported()) {
            URL iconUrl = ClassLoader.getSystemResource("icon.png");
            iconImg = Toolkit.getDefaultToolkit().createImage(iconUrl);
            trayIcon = new TrayIcon(iconImg, "TestTaskBarMonitor");
            trayIcon.setImageAutoSize(true);
            this.addTrayIcon();
            if (TaskBarMonitor.isSupported()) {
                TaskBarMonitor.getInstance().addTaskBarListener(
                        new TaskBarListener() {
                            @Override
                            public void taskBarCreated() {
                                SwingUtilities.invokeLater(new Runnable() {
                                    // 一定要在Swing事件处理线程中调用
                                    // 直接在AWT事件循环线程中调用没有效果
                                    @Override
                                    public void run() {
                                        addTrayIcon();
                                    }
                                });
                            }
                        });
                this.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        // 记得在程序退出时手动关闭,否则会造成内存泄漏
                        TaskBarMonitor.getInstance().setEnable(false);
                    }
                });
                // 启动监视器
                TaskBarMonitor.getInstance().setEnable(true);
            }
        }
    }

    private void addTrayIcon() {
        System.out.println("addTrayIcon called");
        try {
            SystemTray tray = SystemTray.getSystemTray();
            // 总会得到同一个SystemTray对象
            tray.remove(trayIcon);// 一定要先remove
            tray.add(trayIcon);
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new TestTaskBarMonitor().setVisible(true);
    }
}




 

你可能感兴趣的:(解决Java程序的托盘图标在Windows任务栏重建后丢失的问题(使用SystemTray类创建托盘图标))