You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

制作可下载平台专属库的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 13:54:50