解决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 ;

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

 

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

你可能感兴趣的:(windows)