java - 尽管首先检查操作系统,但 Mac OS X 特定的操作会使应用程序在其他操作系统上崩溃

标签 java cross-platform

我使用以下代码将我的 Java 应用程序与 Mac OS X 集成:

if (System.getProperty("os.name").equals("Mac OS X"))
{
    System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText"); // Program name
    com.apple.eawt.Application.getApplication().setDockIconImage(new javax.swing.ImageIcon( JotextMain.class.getResource("icons/Icon.png")  ).getImage()); // Dock icon
    System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
    com.apple.eawt.Application.getApplication().addApplicationListener(new MacAboutAndQuit()); //Make "Quit" and "About" options work
}

在 Mac OS X 上,它工作正常。然而在 Ubuntu 上:

Exception in thread "main" java.lang.NoClassDefFoundError: com/apple/eawt/ApplicationListener
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2451)
    at java.lang.Class.getMethod0(Class.java:2694)
    at java.lang.Class.getMethod(Class.java:1622)
    at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:494)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:486)
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.ApplicationListener
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    ... 6 more

当我注释掉该代码时,它在 Ubuntu 上运行良好。 奇怪,因为它在执行 Mac OS X 特定操作之前首先检查 Mac OS X。 我没有 Windows,但我使用 Windows 的 friend 也报告说我的 .jar 没有启动,而是从 Java 虚拟机启动器中给出了“发生 Java 异常”的消息。在学校的 Windows PC 上,同样的问题。

这是什么原因造成的?

最佳答案

if block 的第二行是问题所在(以及第四行)。即使您的代码从未调用该函数,JVM 仍然必须在初始化包含该 if 的任何类时尝试加载 com.apple.eawt.Application 类> 声明,仅仅是因为对它的引用。当您不在 Mac 上时,该类不存在,因此会出现错误。

解决方法是使用反射来进行该函数调用。这应该可以防止类加载器在实际调用之前尝试加载该类。

示例

javax.swing.ImageIcon icon = ...
Class c = Class.forName("com.apple.eawt.Application");
Method m = c.getMethod("getApplication");
Object applicationInstance = m.invoke(null);
m = applicationInstance.getClass().getMethod("setDockIconImage", javax.swing.ImageIcon.class);
m.invoke(applicationInstance,icon);

这是 if block 中第二行的反射等价物。面对良好的开发实践,有很多关于此的内容 - 如果类/方法名称更改,您不会收到编译器警告,但它确实允许您对特定类型进行调用,而无需在源代码中直接引用它们。这会阻止类加载器加载那些特定于操作系统的类,直到您真正进入 if 语句。

为了稍微清理一下,你可以创建一个特殊的类来处理所有 Mac-OS 特定的东西,将那个功能包装在一个方法中,然后像这个例子一样用反射调用那个方法 - 这会节省你Method m = ... 风格的调用之一。

反射注意事项

除了这段代码丑陋且不易维护之外,反射非常慢。它最适合在这种情况下使用,在这种情况下,您可能会在程序运行期间对某些内容进行一次初始化。通常,出于性能原因(如果不是出于可维护性问题),您希望远离反射。

阅读此内容以获取有关反射性能的更多信息:

Java Reflection Performance

更优雅的解决方案

与其用反射代码替换 if block 中的所有内容,不如创建一个接口(interface),如下所示:

interface SpecificPlatform{
    void initialize(javax.swing.ImageIcon icon);
}

然后,我会为 Mac 实现它:

class MacOSX implements SpecificPlatform{
    @Override
    public void initialize(javax.swing.ImageIcon icon){
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText");
        Application.getApplication().setDockIconImage(new javax.swing.ImageIcon(
            JotextMain.class.getResource("icons/Icon.png")  ).getImage()); // Dock icon
        System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
        Application.getApplication().addApplicationListener(new MacAboutAndQuit());
    }
}

现在...将您的原始代码更改为如下内容:

javax.swing.ImageIcon icon = ...
SpecificPlatform sp = null;

if (System.getProperty("os.name").equals("Mac OS X"))
    sp = (SpecificPlatform)(Class.forName("pkg.name.MacOSX").getConstructor().
            newInstance());
}

//Check for other OS-specific classes here ...

if(sp != null) sp.initialize(icon);

现在每个操作系统只有一个难看的反射调用,而不是为每个必须进行的特定于操作系统的方法调用进行一大堆难看的反射调用。

关于java - 尽管首先检查操作系统,但 Mac OS X 特定的操作会使应用程序在其他操作系统上崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16945753/

相关文章:

java - animateLayoutChanges 单独对元素进行动画处理,即使它们位于 ViewGroup 中

java - Aspectj 和捕获私有(private)或内部方法

java - 如何使单个按钮在 Android 中显示为 Activity 状态

.net-core - 多平台编译 : System. Configuration.ConfigurationManager.GetSection throws error on .NetCore

c - 平台名称宏叫什么? (例如,__SVR4)

php - 如何使用 phing 删除符号链接(symbolic link)?

java - 什么更快 : instanceof or isInstance?

java - 封装 Integer.parseInt() 的好方法

通过将静态构建转换为特定于操作系统的二进制文件来跨操作系统构建

clojure - 建立需要基于构建平台的不同依赖项的 leiningen 项目的优雅方法是什么?