目录
macOS下有些带GUI的程序并没有按照macOS的规范去打包成一个MacOS的Application,导致启动它的体验不太好,虽然不影响使用。
这里以Jadx
-- 一个Java反编译工具, 为例演示如何把一个Java程序包装为MacOS程序 Application。
通过
brew
安装过jadx
之后,可以通过执行jadx-gui
命令来启动他的图形界面。
做个图标
做为一个应用程序,最早呈现给用户的就是图标。macOS 使用 icns 格式的图标资源。图片格式及大小没具体要求,长宽比例必须是1:1
的。
* 可以通过cloudconvert.com在线转换 https://cloudconvert.com/png-to-icns
* 可以通过makeicns
线下转换
1 2 3 |
brew install makeicns makeicns -in myfile.png -out outfile.icns |
改进Java显示效果
Java的GUI启动之后,在dock里还是显示JRE的默认图标,如果需要额外定制图标就需要通过其他参数指定。
Java为macOS提供两个专门的参数来定制在macOS的表现(在macOS上可以通过java -X
命令查看参数描述)。
1 2 3 4 5 |
-Xdock:name=<application name> override default application name displayed in dock -Xdock:icon=<path to icon file> override default icon displayed in dock |
-Xdock:name 用来指定在菜单栏里显示的名字。在macOS早期的版本是可以定制在dock中显示的名字。现在不行了。
-Xdock:icon= 用来指定在dock中显示的图标。
jadx-gui 支持通过JADX_GUI_OPTS
变量给他传参数,其他Java程序按自己实际情况调整。
Automator
Automator 是macOS自带的自动化工具。
1. 启动 Automator 并创建一个 Application
类型的文件。
2. 添加一个Run Shell Script
的action。Pass input
改为as arguments
并在下方的文本框内填入如下命令
1 2 |
JADX_GUI_OPTS="-Xdock:icon=/Applications/Jadx.app/Contents/Resources/ApplicationStub.icns -Xdock:name=Jadx" /usr/local/bin/jadx-gui "$@" |
- 点击文件-保存。名字为
Jadx.app
, 位置选/Application
,文件类型为Application
- 把定制的图标重命名为
ApplicationStub.icns
,然后覆盖位于/Application/Jadx.app/Contents/Resources/ApplicationStub.icns
的Automator默认图标。
> 在 Xxx.app 上点右键,选择显示包内容(Show Package Contents)
,就可以以文件夹的方式打开Xxx.app了。
universalJavaApplicationStub
使用 Automator 包装的程序制作简单,唯独在dock中显示的名字不能修改,略有遗憾。参考了JD-GUI
的实现,我发现了universalJavaApplicationStub。
miniBundleApp
最小可运行的macOS程序结构大概是这个样子。
* Info.plist 存储程序相关的属性配置信息。
* MacOS 目录存放可执行程序
* Resources 存放资源文件, 示例中放了图标资源。而像JD-GUI就把jar包放在Resources/Java
目录下。
1 2 3 4 5 6 7 8 |
Demo.app └── Contents ├── Info.plist ├── MacOS │ └── universalJavaApplicationStub └── Resources └── ApplicationStub.icns |
- 创建目录结构。
1 2 3 4 |
mkdir -p /Applications/Jadx-gui.app/Contents/{MacOS,Resources} curl https://raw.githubusercontent.com/tofi86/universalJavaApplicationStub/master/src/universalJavaApplicationStub -o /Applications/Jadx-gui.app/Contents/MacOS/universalJavaApplicationStub chmod +x /Applications/Jadx-gui.app/Contents/MacOS/universalJavaApplicationStub |
- 配置Info.plist
Info.plist 位于Demo.app/Contents/Info.plist
。
主要关系几个属性:CFBundleName、CFBundleIdentifier、MainClass、ClassPath。
* CFBundleName 程序名称
* CFBundleIdentifier 程序唯一标识
* MainClass Java的启动class
* ClassPath Java的classpath路径。要注意的是如果要配置目录时,lib/*
需要对*
做转义lib/\\*
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>universalJavaApplicationStub</string> <key>CFBundleName</key> <string>Jadx</string> <key>CFBundleGetInfoString</key> <string>Jadx for macOS</string> <key>CFBundleIconFile</key> <string>ApplicationStub.icns</string> <key>CFBundleIdentifier</key> <string>org.xobo.Jadx</string> <key>CFBundleShortVersionString</key> <string>1.0.0</string> <key>NSHumanReadableCopyright</key> <string>Copyright 2022 xobo</string> <key>JavaX</key> <dict> <key>MainClass</key> <string>jadx.gui.JadxGUI</string> <key>JVMVersion</key> <string>1.8+</string> <key>ClassPath</key> <string>/usr/local/Cellar/jadx/1.4.4/libexec/lib/\\*</string> <key>WorkingDirectory</key> <string>$APP_ROOT</string> <key>Properties</key> <dict> <key>apple.laf.useScreenMenuBar</key> <string>true</string> </dict> </dict> <key>CFBundlePackageType</key> <string>APPL</string> <key>CSResourcesFileMapped</key> <true/> <key>LSRequiresCarbon</key> <true/> <key>NSPrincipalClass</key> <string>NSApplication</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>NSHighResolutionCapable</key> <true/> <key>NSAppleEventsUsageDescription</key> <string>There was an error while launching the application. Please click OK to display a dialog with more information or cancel and view the syslog for details.</string> </dict> </plist> |
- 跟 Automator 一样替换图标文件即可。
封装依赖
之前做的这些更准确的说是包装而不是封装,因为我们的程序极度的依赖外部的文件,这个Application 打包给别人并不能做为独立应用直接使用。如果希望能做为独立应用还需要做以下工作:
程序内添加应用依赖
本文整体都是以 Apple 风格的Java程序,如果希望以Oracle风格构建Application可以查看
universalJavaApplicationStub
文档及源码。
在Resources
目录下创建Java
目录。Demo.app/Contents/Resources/Java
,然后把jadx所有依赖的jar包复制到这个目录下。
然后配置classpath为$JAVAROOT/\\*
。
打包JRE
如果需要把JRE一起打包进来用jlink(jdk9+) 或 jpackage(jdk8)更合适。
universalJavaApplicationStub
的JAVA_HOME支持相对路径。在Info.plist内配置LSEnvironment
并复制JRE到对应位置就可以了。
1 2 3 4 5 6 |
<key>LSEnvironment</key> <dict> <key>JAVA_HOME</key> <string>Contents/Frameworks/jdk8u232-b09-jre/Contents/Home</string> <dict> |
macOS 10.15+ 可能会遇到的问题
从macOS 10.15开始,macOS 默认会阻止对受保护资源的访问,如用户的下载、文档或桌面文件夹,并显示一个安全对话框,用户必须接受该对话框才能被允许访问。
当在你的应用程序中使用javax.swing.JFileChooser时,它支持这些类型的安全对话框(有趣的是java.awt.FileDialog不支持!),你应该使用universalJavaApplicationStub脚本的编译二进制文件而不是普通的bash脚本。
二进制文件有两个不同的版本,根据自己的系统选择。 universalJavaApplicationStub-xxx-binary-macos-10.15.zip 的可能可以在 macOS 11 上使用, universalJavaApplicationStub-xxx-binary-macos-11.0.zip 肯定不能在 10.15 的系统上使用。
打开方式 Open With
如果希望在class、jar、java、zip等文件上点右键能出现该程序,只需要在Info.plist里添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
<key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeExtensions</key> <array> <string>class</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/java</string> </array> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>CFBundleTypeName</key> <string>Class File</string> <key>LSIsAppleDefaultForType</key> <true/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>java</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>text/plain</string> </array> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>CFBundleTypeName</key> <string>Java File</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>jar</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/java-archive</string> </array> <key>CFBundleTypeName</key> <string>Jar File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>war</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/java-archive</string> </array> <key>CFBundleTypeName</key> <string>War File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>ear</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/java-archive</string> </array> <key>CFBundleTypeName</key> <string>Ear File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>aar</string> </array> <key>CFBundleTypeName</key> <string>Android archive File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>jmod</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/java-archive</string> </array> <key>CFBundleTypeName</key> <string>Java module File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> <dict> <key>CFBundleTypeExtensions</key> <array> <string>zip</string> </array> <key>CFBundleTypeMIMETypes</key> <array> <string>application/zip</string> </array> <key>CFBundleTypeName</key> <string>Zip File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> <key>LSIsAppleDefaultForType</key> <false/> <key>LSTypeIsPackage</key> <false/> </dict> </array> |
universalJavaApplicationStub-Info.plist
https://gist.github.com/cnxobo/e53e0571c5165913e68c8db3600c7d29
评论关闭。