2017-03-15 32 views
0

我正在開發一個使用java swing的簡單應用程序,我想在Windows,MacOS和Linux中使用它。 當然,我試圖將它與操作系統整合到最佳狀態。在非MACOS上捕獲java.lang.NoClassDefFoundError JRE

對於MacOS,我有這段代碼可以讓我在全局菜單和「關於」按鈕的操作中設置應用程序名稱。

我用下面的代碼:

if(System.getProperty("os.name").toUpperCase().startsWith("MAC")){ 
     System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My app name"); 
     System.setProperty("apple.awt.application.name", "My app name"); 
     //Need for macos global menubar 
     System.setProperty("apple.laf.useScreenMenuBar", "true"); 
     try{ 
     com.apple.eawt.Application app = com.apple.eawt.Application.getApplication(); 
     app.setDockIconImage(Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png"))); 
     app.setAboutHandler(new com.apple.eawt.AboutHandler() { 
      @Override 
      public void handleAbout(com.apple.eawt.AppEvent.AboutEvent aboutEvent) { 
       AboutDialog a = new AboutDialog(); 
       a.setTitle("About"); 
       a.pack(); 
       a.setResizable(false); 
       centerDialogInScreen(a); 
       a.setVisible(true); 
      } 
     }); 
     } catch (Throwable e){ 
      //This means that the application is not being run on MAC OS. 
      //Just do nothing and go on... 
     } 
    } 

當我運行我在非JAVA的MacOS自JRE沒有com.apple.eawt應用*類的JVM應該拋出一個NoDefClassFoundError,我趕上,繼續吧?

這似乎

並沒有那樣做,當我啓動我的應用程序「的.jar」我得到以下(在Windows上):

Error: A JNI error has occurred, please check your installation and try again 
Exception in thread "main" java.lang.NoClassDefFoundError: com/apple/eawt/AboutHandler 
     at java.lang.Class.getDeclaredMethods0(Native Method) 
     at java.lang.Class.privateGetDeclaredMethods(Unknown Source) 
     at java.lang.Class.privateGetMethodRecursive(Unknown Source) 
     at java.lang.Class.getMethod0(Unknown Source) 
     at java.lang.Class.getMethod(Unknown Source) 
     at sun.launcher.LauncherHelper.validateMainClass(Unknown Source) 
     at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source) 
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.AboutHandler 
     at java.net.URLClassLoader.findClass(Unknown Source) 
     at java.lang.ClassLoader.loadClass(Unknown Source) 
     at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) 
     at java.lang.ClassLoader.loadClass(Unknown Source) 
     ... 7 more 

缺少什麼我在這裏?

+0

我會創建本地副本AboutHandler和AboutEvent與原始簽名和包裝聲明相同 – HRgiger

+0

代碼中的「else」部分是什麼? –

+0

@HRgiger我試過使用OpenJDKs這個類的源代碼,但發現這是一個愚蠢的嘗試,因爲它會增加包的大小。 如果我創建了這些文件,我將如何讓MACOS JVM選擇安裝的文件而不是我的包上的文件? 作爲最後的手段,我可​​以爲每個平臺創建一個不同的主文件,併爲每個平臺生成一個不同的應用程序jar。但是,因爲我很懶(我認爲這是一種很好的方式),我想爲所有平臺生成一個包。 – loveMeansNothing

回答

0

嗯,我設法解決部分問題使用反射。圖標部分工作正常,我認爲如果我能夠將代碼翻譯爲反射,「AboutHandler」部分將工作得很好。 因爲它不重要,我有一些更大的魚來炒我沒有太多打擾。 如果有人管理如何使用反射來獲得AboutHandler,我會將其標記爲正確。

if(System.getProperty("os.name").toUpperCase().startsWith("MAC")) { 
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My App Name"); 
System.setProperty("apple.awt.application.name", "My App Short name"); 
//Need for macos global menubar 
System.setProperty("apple.laf.useScreenMenuBar", "true"); 

try { 
    Class application = Class.forName("com.apple.eawt.Application"); 
    Method getApplication = application.getMethod("getApplication"); 
    Object instance = getApplication.invoke(application); 

    Class[] params = new Class[1]; 
    params[0] = Image.class; 
    Method setIcon = application.getMethod("setDockIconImage",params); 
    setIcon.invoke(instance,Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png"))); 

} catch (ClassNotFoundException | NoSuchMethodException | 
     SecurityException | IllegalAccessException | 
     IllegalArgumentException | InvocationTargetException exp) { 
    exp.printStackTrace(System.err); 
} 
} 
1

apple類不是標準的Java類,因此不能在Mac平臺之外使用。如果您希望您的應用程序跨平臺,最好的選擇是避免使用這些類,即使這意味着與MacOS的整合程度較低。

在另一方面,你可以創建一個特定的「關於」類不同的操作系統,並且具有擴展爲特定操作系統(如MacAbout,WinAbout,....)

確定類的名字運行時,並使用Class.forName來動態加載類,從而避免JRE需要解析不存在的類。

但是,老實說,我會選擇第一個選項,並創建一個真正的平臺獨立應用程序。

+1

這種競爭[示例](http://stackoverflow.com/a/30308671/230513)說明了'Class.forName()'方法;另見'OSXAdapter',引用[here](http://stackoverflow.com/a/2061318/230513)。 – trashgod

2

NoClassDefFoundError將被拋出的確切時間不是固定的,但可能取決於JVM實現細節。就你而言,這是對班級的驗證。在HotSpot中,Verifier不會加載所有引用的類,但顯然它試圖在你的情況下加載AboutHandler,這可能是由於你的類有一個實現AboutHandler的內部類以及驗證者希望檢查類型層次結構的一致性,即AboutHandler是否真的是一個接口。確切的細節並不重要,因爲即使您設法解決它,結果也會很脆弱,並且可能會在其他版本或其他JVM實現中突然崩潰。

如果您想安全起見,您不能直接引用可能不存在的類。因此,您可以通過使用Class.forNameMethod.invoke等反射性地完成整個操作,但這會使任何不平凡的代碼變得繁瑣。更簡單的解決方案是將整個MacOS特定代碼放入其自己的類中,例如MacSetup。然後,您只需通過Class.forName(僅在檢查您在MacOS上運行後)加載此課程才能將其分離。

public class MacSetup implements Runnable { 
    @Override 
    public void run() { 
     System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My app name"); 
     System.setProperty("apple.awt.application.name", "My app name"); 
     //Need for macos global menubar 
     System.setProperty("apple.laf.useScreenMenuBar", "true"); 
     com.apple.eawt.Application app = com.apple.eawt.Application.getApplication(); 
     app.setDockIconImage(Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png"))); 
     app.setAboutHandler(new com.apple.eawt.AboutHandler() { 
      @Override 
      public void handleAbout(com.apple.eawt.AppEvent.AboutEvent aboutEvent) { 
       AboutDialog a = new AboutDialog(); 
       a.setTitle("About"); 
       a.pack(); 
       a.setResizable(false); 
       centerDialogInScreen(a); 
       a.setVisible(true); 
      } 
     }); 
    } 
} 

主要類:

if(System.getProperty("os.name").toUpperCase().startsWith("MAC")) { 
    try { 
     Class.forName("MacSetup").asSubclass(Runnable.class).newInstance().run(); 
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { 
     // since this code is only executed when we *are* on MacOS, we should report it 
     Logger.getLogger("MacSetup").log(Level.SEVERE, null, ex); 
    } 
} 

所以主類是從MacSetup類及其所有參考的分離,因爲它反射性地加載這個類,並通過始終存在Runnable調用它的實現方法界面,將反射操作減少到必要的最小值。

+0

你一定知道你的java先生。主要的問題是設置應用程序圖標和關於句柄代碼的設置必須在主線程中運行(至少是它似乎工作的唯一方式)。 – loveMeansNothing

+0

它在主線程中運行。 Runnable接口的使用並不意味着多線程,在這裏,它只是作爲普通接口使用,沒有參數返回'void'的方法。 – Holger

+0

我明白了。你的方法似乎在macOS中打破了圖標和aboutHandler,但它確實是一個聰明而乾淨的... – loveMeansNothing

0

的com.apple Java包已被取消,在java9刪除(現在在Mac OS默認高塞拉利昂)

看到http://openjdk.java.net/jeps/272在新的API使用

package java.awt; 

public class Desktop { 

    /* ... */ 

    /** 
    * Adds sub-types of {@link AppEventListener} to listen for notifications 
    * from the native system. 
    * 
    * @param listener 
    * @see AppForegroundListener 
    * @see AppHiddenListener 
    * @see AppReOpenedListener 
    * @see AppScreenSleepListener 
    * @see AppSystemSleepListener 
    * @see AppUserSessionListener 
    */ 

    public void addAppEventListener(final AppEventListener listener) {} 

    /** 
    * Requests user attention to this application (usually through bouncing the Dock icon). 
    * Critical requests will continue to bounce the Dock icon until the app is activated. 
    * 
    */ 
    public void requestUserAttention(final boolean critical) {} 

    /** 
    * Attaches the contents of the provided PopupMenu to the application's Dock icon. 
    */ 
    public void setDockMenu(final PopupMenu menu) {} 

    /** 
    * Changes this application's Dock icon to the provided image. 
    */ 
    public void setDockIconImage(final Image image) {} 


    /** 
    * Affixes a small system provided badge to this application's Dock icon. Usually a number. 
    */ 
    public void setDockIconBadge(final String badge) {} 

    /** 
    * Displays or hides a progress bar or other indicator in 
    * the dock. 
    * 
    * @see DockProgressState.NORMAL 
    * @see DockProgressState.PAUSED 
    * @see DockProgressState.ERROR 
    * 
    * @see #setDockProgressValue 
    */ 
    public void setDockProgressState(int state) {} 

    /** 
    * Sets the progress bar's current value to {@code n}. 
    */ 
    public void setDockProgressValue(int n) {} 

    /** 
    * Tests whether a feature is supported on the current platform. 
    */ 

    public boolean isSupportedFeature(Feature f) {} 

    /* ... */ 
}