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;

测试
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(0016090);
        
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);
    }
}

 

在32位WinXP和64位Win7下测试有效(需要分别编译x86和x64的DLL)

posted on 2010-05-28 20:37  高町⑥⑥○  阅读(1139)  评论(0编辑  收藏  举报