制作可下载平台专属库的Java可运行JAR包
解决方案:轻量启动JAR + 运行时自动下载平台Native库
一、调整Gradle配置,打包无Native依赖的轻量JAR
修改build.gradle.kts,创建一个仅包含应用代码和纯Java依赖的thinJar任务,排除所有平台专属的native库(比如OpenCV、OpenBLAS的多平台artifact):
val thinJar = task("thinJar", type = Jar::class) { archiveBaseName.set("${project.name}-thin") manifest { attributes["Implementation-Title"] = project.name attributes["Implementation-Version"] = project.version attributes["Main-Class"] = "com.yourpackage.Launcher" // 指定新的启动类,而非原业务主类 } duplicatesStrategy = DuplicatesStrategy.EXCLUDE // 打包应用编译后的类文件 from(sourceSets.main.get().output) // 引入纯Java依赖,过滤平台相关native库 val runtimeClasspath = configurations.runtimeClasspath.get() from(runtimeClasspath.filter { // 排除OpenCV的平台专属JAR(示例格式:opencv-4.x-windows-x86_64.jar) !it.name.contains("opencv-") || !it.name.matches(Regex(".*-(windows|linux|macosx)-.*")) // 追加OpenBLAS的平台库排除规则,根据实际依赖名调整 && !it.name.contains("openblas-") || !it.name.matches(Regex(".*-(x86_64|arm64)-.*")) }.map { if (it.isDirectory) it else zipTree(it) }) } // 可选:让build任务默认执行thinJar tasks.build { dependsOn(thinJar) }
二、编写启动类,实现平台检测与库下载
创建Launcher类作为应用入口,先完成native库的下载、校验与加载,再启动业务主逻辑。以下是Kotlin实现示例:
package com.yourpackage import java.io.File import java.io.FileOutputStream import java.net.URL import java.nio.channels.Channels import java.nio.file.Files import java.nio.file.Paths import java.security.MessageDigest import java.util.* class Launcher { companion object { // 缓存目录:用户主目录下的专属缓存文件夹 private val APP_CACHE_DIR = Paths.get(System.getProperty("user.home"), ".cache", "your-app-name").toFile() // 平台与对应库的映射:键为"OS-架构",值为(下载URL, MD5校验值) private val PLATFORM_LIBS = mapOf( "Windows-x86_64" to Pair( "https://your-host.com/libs/opencv-4.8.0-windows-x86_64.jar", "abcdef1234567890abcdef1234567890" ), "Linux-x86_64" to Pair( "https://your-host.com/libs/opencv-4.8.0-linux-x86_64.jar", "1234567890abcdef1234567890abcdef" ), "MacOS-x86_64" to Pair( "https://your-host.com/libs/opencv-4.8.0-macosx-x86_64.jar", "abc123def456ghi789jkl012mno345pqr" ), "MacOS-arm64" to Pair( "https://your-host.com/libs/opencv-4.8.0-macosx-arm64.jar", "def456ghi789jkl012mno345pqr678stu" ) // 补充OpenBLAS对应平台的下载信息 ) @JvmStatic fun main(args: Array<String>) { try { // 1. 检测当前运行平台 val os = getOsName() val arch = getArch() val platformKey = "$os-$arch" val libInfo = PLATFORM_LIBS[platformKey] ?: error("不支持当前平台:$platformKey") // 2. 初始化缓存目录 if (!APP_CACHE_DIR.exists()) APP_CACHE_DIR.mkdirs() // 3. 检查库是否已存在且校验通过 val libFile = File(APP_CACHE_DIR, libInfo.first.substringAfterLast("/")) if (!libFile.exists() || !checkMd5(libFile, libInfo.second)) { println("正在下载平台专属依赖库...") downloadFile(libInfo.first, libFile) if (!checkMd5(libFile, libInfo.second)) { error("下载文件校验失败,请重试") } } // 4. 将native库JAR动态加入类路径 addJarToClasspath(libFile) // 5. 启动业务主类 val mainClass = Class.forName("com.yourpackage.Main") val mainMethod = mainClass.getMethod("main", Array<String>::class.java) mainMethod.invoke(null, args) } catch (e: Exception) { println("应用初始化失败:${e.message}") e.printStackTrace() System.exit(1) } } // 识别操作系统 private fun getOsName(): String { val os = System.getProperty("os.name").lowercase(Locale.getDefault()) return when { os.contains("win") -> "Windows" os.contains("mac") -> "MacOS" os.contains("nux") || os.contains("nix") -> "Linux" else -> error("未知操作系统:$os") } } // 识别CPU架构 private fun getArch(): String { val arch = System.getProperty("os.arch").lowercase(Locale.getDefault()) return when { arch.contains("amd64") || arch.contains("x86_64") -> "x86_64" arch.contains("aarch64") || arch.contains("arm64") -> "arm64" else -> error("未知CPU架构:$arch") } } // 下载文件到指定路径 private fun downloadFile(url: String, dest: File) { URL(url).openStream().use { inputStream -> Channels.newChannel(inputStream).use { channel -> FileOutputStream(dest).use { outputStream -> outputStream.channel.transferFrom(channel, 0, Long.MAX_VALUE) } } } } // MD5校验文件 private fun checkMd5(file: File, expectedMd5: String): Boolean { val md = MessageDigest.getInstance("MD5") Files.newInputStream(file.toPath()).use { inputStream -> val buffer = ByteArray(8192) var bytesRead: Int while (inputStream.read(buffer).also { bytesRead = it } != -1) { md.update(buffer, 0, bytesRead) } } val digest = md.digest() val hexString = StringBuilder() for (b in digest) { hexString.append(String.format("%02x", b)) } return hexString.toString() == expectedMd5.lowercase() } // 动态添加JAR到系统类加载器 private fun addJarToClasspath(jarFile: File) { val url = jarFile.toURI().toURL() val classLoader = ClassLoader.getSystemClassLoader() as java.net.URLClassLoader val method = java.net.URLClassLoader::class.java.getDeclaredMethod("addURL", URL::class.java) method.isAccessible = true method.invoke(classLoader, url) } } }
三、关键注意事项
- Native库托管:需将各平台的native库(如OpenCV平台JAR、OpenBLAS原生库包)上传至静态服务器(自建HTTP服务、云存储均可),确保用户可直接下载。
- 安全校验:必须添加MD5/SHA校验,避免文件损坏或被篡改。
- 平台覆盖:根据目标用户覆盖所有需要支持的OS与架构组合,避免出现不兼容报错。
- 缓存优化:使用用户目录缓存,避免重复下载,提升后续启动速度。
- 错误处理:完善异常捕获逻辑,给用户清晰的错误提示(如网络问题、平台不支持)。
内容的提问来源于stack exchange,提问作者Benjamin H
相关产品推荐
相关产品推荐

