创build跨平台的Java SWT应用程序
我已经使用SWT编写了一个Java GUI。 我使用ANT脚本打包应用程序(以下片段)。
<jar destfile="./build/jars/swtgui.jar" filesetmanifest="mergewithoutmain"> <manifest> <attribute name="Main-Class" value="org.swtgui.MainGui" /> <attribute name="Class-Path" value="." /> </manifest> <fileset dir="./build/classes" includes="**/*.class" /> <zipfileset excludes="META-INF/*.SF" src="lib/org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar" /> </jar>
这产生了一个单一的jar在Windows上,我只需双击运行我的GUI。 缺点是我不得不明确包装窗口SWT包到我的jar子里。
我希望能够在其他平台(主要是Linux和OS X)上运行我的应用程序。 最简单的方法是创build平台特定的jar包,将相应的SWT文件打包成单独的JAR文件。
有没有更好的方法来做到这一点? 是否可以创build一个可以在多个平台上运行的JAR?
我刚刚遇到了同样的问题。 我还没有尝试过,但我打算为所有平台包含swt.jar
版本,并在main
方法的开始处dynamic加载正确的版本。
更新:它的工作。 build.xml
包含所有jar包:
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_linux_gtk_x86.jar"/> <zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_macosx_x86.jar"/> <zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_win32_x86.jar"/> <zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_linux_gtk_x64.jar"/> <zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_macosx_x64.jar"/> <zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_win32_x64.jar"/>
我的main
方法开始调用这个:
private void loadSwtJar() { String osName = System.getProperty("os.name").toLowerCase(); String osArch = System.getProperty("os.arch").toLowerCase(); String swtFileNameOsPart = osName.contains("win") ? "win32" : osName.contains("mac") ? "macosx" : osName.contains("linux") || osName.contains("nix") ? "linux_gtk" : ""; // throw new RuntimeException("Unknown OS name: "+osName) String swtFileNameArchPart = osArch.contains("64") ? "x64" : "x86"; String swtFileName = "swt_"+swtFileNameOsPart+"_"+swtFileNameArchPart+".jar"; try { URLClassLoader classLoader = (URLClassLoader) getClass().getClassLoader(); Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); addUrlMethod.setAccessible(true); URL swtFileUrl = new URL("rsrc:"+swtFileName); // I am using Jar-in-Jar class loader which understands this URL; adjust accordingly if you don't addUrlMethod.invoke(classLoader, swtFileUrl); } catch(Exception e) { throw new RuntimeException("Unable to add the SWT jar to the class path: "+swtFileName, e); } }
[编辑]对于那些寻找“jar-in-jar classloader”的人来说:它包含在Eclipse的JDT(构build在Eclipse上的Java IDE)中。 用archiver打开org.eclipse.jdt.ui_*version_number*.jar
,你会在里面find一个文件jar-in-jar-loader.zip
。
我有一个工作实现,现在从SWT FAQ中引用。
这种方法现在可用作ANT任务: SWTJar
[编辑]如上所述,SWTJar现已更新为使用Alexey Romanov的解决scheme。
build.xml文件
首先我build立一个包含我所有应用程序类的jar。
<!-- UI (Stage 1) --> <jarjar jarfile="./build/tmp/intrace-ui-wrapper.jar"> <fileset dir="./build/classes" includes="**/shared/*.class" /> <fileset dir="./build/classes" includes="**/client/gui/**/*.class" /> <zipfileset excludes="META-INF/*.MF" src="lib/miglayout-3.7.3.1-swt.jar"/> </jarjar>
接下来,我build立一个jar来包含以下所有内容:
- JAR文件
- 我刚刚build立的jar子
- 所有的SWTjar子
- 类
- “Jar-in-Jar”类加载器类
- 一个特殊的装载机类 – 见下文
这是来自build.xml的片段。
<!-- UI (Stage 2) --> <jarjar jarfile="./build/jars/intrace-ui.jar"> <manifest> <attribute name="Main-Class" value="org.intrace.client.loader.TraceClientLoader" /> <attribute name="Class-Path" value="." /> </manifest> <fileset dir="./build/classes" includes="**/client/loader/*.class" /> <fileset dir="./build/tmp" includes="intrace-ui-wrapper.jar" /> <fileset dir="./lib" includes="swt-*.jar" /> <zipfileset excludes="META-INF/*.MF" src="lib/jar-in-jar-loader.jar"/> </jarjar>
TraceClientLoader.java
这个装载器类使用jar-in-jar-loader来创build一个ClassLoader,它从两个瓶子中加载类。
- 正确的SWTjar子
- 应用程序包装jar
一旦我们有了这个类加载器,我们可以使用reflection来启动实际的应用程序主方法。
public class TraceClientLoader { public static void main(String[] args) throws Throwable { ClassLoader cl = getSWTClassloader(); Thread.currentThread().setContextClassLoader(cl); try { try { System.err.println("Launching InTrace UI ..."); Class<?> c = Class.forName("org.intrace.client.gui.TraceClient", true, cl); Method main = c.getMethod("main", new Class[]{args.getClass()}); main.invoke((Object)null, new Object[]{args}); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof UnsatisfiedLinkError) { System.err.println("Launch failed: (UnsatisfiedLinkError)"); String arch = getArch(); if ("32".equals(arch)) { System.err.println("Try adding '-d64' to your command line arguments"); } else if ("64".equals(arch)) { System.err.println("Try adding '-d32' to your command line arguments"); } } else { throw ex; } } } catch (ClassNotFoundException ex) { System.err.println("Launch failed: Failed to find main class - org.intrace.client.gui.TraceClient"); } catch (NoSuchMethodException ex) { System.err.println("Launch failed: Failed to find main method"); } catch (InvocationTargetException ex) { Throwable th = ex.getCause(); if ((th.getMessage() != null) && th.getMessage().toLowerCase().contains("invalid thread access")) { System.err.println("Launch failed: (SWTException: Invalid thread access)"); System.err.println("Try adding '-XstartOnFirstThread' to your command line arguments"); } else { throw th; } } } private static ClassLoader getSWTClassloader() { ClassLoader parent = TraceClientLoader.class.getClassLoader(); URL.setURLStreamHandlerFactory(new RsrcURLStreamHandlerFactory(parent)); String swtFileName = getSwtJarName(); try { URL intraceFileUrl = new URL("rsrc:intrace-ui-wrapper.jar"); URL swtFileUrl = new URL("rsrc:" + swtFileName); System.err.println("Using SWT Jar: " + swtFileName); ClassLoader cl = new URLClassLoader(new URL[] {intraceFileUrl, swtFileUrl}, parent); try { // Check we can now load the SWT class Class.forName("org.eclipse.swt.widgets.Layout", true, cl); } catch (ClassNotFoundException exx) { System.err.println("Launch failed: Failed to load SWT class from jar: " + swtFileName); throw new RuntimeException(exx); } return cl; } catch (MalformedURLException exx) { throw new RuntimeException(exx); } } private static String getSwtJarName() { // Detect OS String osName = System.getProperty("os.name").toLowerCase(); String swtFileNameOsPart = osName.contains("win") ? "win" : osName .contains("mac") ? "osx" : osName.contains("linux") || osName.contains("nix") ? "linux" : ""; if ("".equals(swtFileNameOsPart)) { throw new RuntimeException("Launch failed: Unknown OS name: " + osName); } // Detect 32bit vs 64 bit String swtFileNameArchPart = getArch(); String swtFileName = "swt-" + swtFileNameOsPart + swtFileNameArchPart + "-3.6.2.jar"; return swtFileName; } private static String getArch() { // Detect 32bit vs 64 bit String jvmArch = System.getProperty("os.arch").toLowerCase(); String arch = (jvmArch.contains("64") ? "64" : "32"); return arch; } }
如上所述,对于那些寻找“jar-in-jar classloader”的人来说:它包含在Eclipse的JDT(构build在Eclipse上的Java IDE)中。 用archiver打开org.eclipse.jdt.ui_ * version_number * .jar,你会在里面find一个文件jar-in-jar-loader.zip。 我将其重命名为jar-in-jar-loader.jar。
intrace-ui.jar – 这是我使用上述过程build立的jar。 你应该可以在win32 / 64,linux32 / 64和osx32 / 64上运行这个jar文件。
[编辑]这个答案现在参考SWT常见问题 。
如果您不打算将所有内容汇总到单个jar文件中并使用jar-in-jar,则还可以通过在您部署的应用程序的lib目录中为每个目标平台包含命名的SWT jar来解决此问题:
lib/swt_win_32.jar lib/swt_win_64.jar lib/swt_linux_32.jar lib/swt_linux_64.jar
并通过使用System.getProperty(String name)
在运行时检查Java系统属性"os.name"
和"os.arch"
来创build正确的jar文件名,从而在运行时dynamic加载正确的文件。
然后,通过调用通常受保护的方法URLClassloader.addURL(URL url)
,在需要第一个SWT类之前,将正确的jar添加到系统类加载器的类path中,然后可以使用稍微顽皮一点的reflection(OO纯粹主义者将目光转向现在!
如果你能站在代码的气味,我已经把一个工作的例子在这里http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191
很奇怪,这里的所有答案只是build议将所有SWT JAR打包成一个巨大的应用程序JAR文件。 恕我直言,这严格违背了SWT的目的:每个平台都有一个SWT库,所以应该只打包每个平台的SWT库。 这很容易做到,只需在你的ANT版本中定义5个构buildconfiguration文件:win32,win64,linux32,linux64和mac64(你也可以做mac32,但所有现代的Mac都是64位的)。
无论如何,如果你想在操作系统中有很好的应用程序集成,那么你将不得不做一些特定于操作系统的事情,然后再次使用构buildconfiguration文件。 对于桌面应用程序,为开发者和用户使用一个应用程序包是不方便的。
将src =“lib / org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar ”中的粗体选定文本replace为linux指定的swt jar文件