# jstart **Repository Path**: zelde/jstart ## Basic Information - **Project Name**: jstart - **Description**: windows、linux上的java应用启动器 - **Primary Language**: Java - **License**: WTFPL - **Default Branch**: jdk8 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-09-03 - **Last Updated**: 2025-05-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jstart > java 应用程序的打包、启动、自更新工具 ## 介绍 - java 9+ 提供了模块化 - java 14 官方打包工具回归了 - jdk lts 到 21 了 **所以不要造轮子了!** ## 目标 1. 启用模块化,使用 jlink 生成更小的运行时 2. 生成平台相关的可执行文件启动器 3. 可配置的jre搜索顺序、虚拟机参数、类路径、java启动类、main方法参数 4. 在线检测新版本并更新 5. 支持java 11及以上版本 ## 应用发布时的目录结构 | 目录/文件 | 图形/控制台 | 说明 | | ------------------ | ----------- | ---------------------------------------------- | | app/ | 图形界面 | javapackager 生成的目录, 仅图形界面程序使用 | | jre/ | 控制台界面 | 控制台启动器优先使用的运行时 | | runtime/ | 图形界面 | 使用 jlink 生成的运行时,图形界面启动器优先使用 | | update/ | 通用 | 更新时使用的临时目录 | | jstart.exe | 图形界面 | 图形界面启动器 | | jstart.ico | 图形界面 | javapackager 生成的启动器图标 | | jstart.log | 通用 | 启动器启动时的日志 | | jstart.properties | 通用 | 启动器的配置文件 | | jstart-bundle.jar | 通用 | 启动器的实现 | | jstartc.exe | 控制台界面 | 控制台界面(如: 服务端)启动器 | | jstartc.ini | 控制台界面 | 控制台启动器的配置文件 | | msvcp140.dll | 图形界面 | 图形界面启动器依赖的 windows 运行时 | | packager.dll | 图形界面 | 图形界面启动器依赖的 windows 运行时 | | pid | 通用 | 启动器启动时记录的pid | | vcruntime140.dll | 图形界面 | 图形界面启动器依赖的 windows 运行时 | | vcruntime140_1.dll | 图形界面 | 图形界面启动器依赖的 windows 运行时 | | version | 通用 | 当前应用的版本, 与更新服务器比较是否是最新版 | ## 关于版本 目前主流的版本风格有 [语义化版本 2.0.0 | Semantic Versioning (semver.org)](https://semver.org/lang/zh-CN/) 和 [日历化版本 / Calendar Versioning — CalVer](https://calver.org/overview_zhcn.html) - 语义化版本 格式: `--[-+]` 主要关注**依赖的稳定性和兼容性**。初始版本一般为:1.0.0 或 0.1.0 : 主版本号, 当你做了不兼容的 API 修改时增加 : 次版本号, 当你做了向下兼容的功能性新增 : 修订号, 当你做了向下兼容的问题修正时增加 : 可选的先行版本号 : 可选的构建(编译)信息 - 日历化版本 日历化版本(CalVer)不是基于任意数字,而是基于项目发布日期 的版本控制约定。认为: **版本随着时间的推移会变得更好。** 对于维护者,版本使我们在不断扩大的生态系统中 可以指定精确的依赖关系。对于卖家和推广者来说, 项目的版本是一个品牌的活力部分。对我们所有人来说, 版本使我们在升级到将来时可以参考过去。 日历化版本没有规定版本号的格式, 而是提供了日期相关的元素给使用者: - **YYYY** - 四位数的年份长格式: 1999, 2000, 2008, 2024 ... - **YY** - 年份的短格式(一般认为是2000年以后的年份): 0, 8, 24 ... - **0Y** - 填充零的年份(一般认为是2000年以后的年份): 00, 08, 24 ... - **MM** - 不填充零的月份格式: 1,2 ... 11, 12 - **0M** - 填充零的月份格式: 01, 02 ... 11, 12 ... - **WW** - 不填充零的一年内周(星期)的序数: 1, 2 ... 51, 52 - **0W** - 填充零的一年内周(星期)的序数: 01, 02 ... 51, 52 - **DD** - 不填充零的日: 1, 2 ... 30, 31 - **0D** - 填充零的日: 01, 02 ... 30, 31 日期无关的元素: - **Major** - 主要版本 - **Minor** - 次要版本 - **Micro** - 微小变更, 或者称为“补丁” - **Modifier** - 修饰词, 如: “dev”、“alpha”、“beta”、“rc1” - 日历化版本的样例 - Ubunt YY.0M.MICRO, 2004年10月发布的第一个版本: 4.10, 目前最新版本: 24.01.1 LTS、24.10 - [pip](https://pip.pypa.io/en/stable/news/) YY.MINOR.MICRO, 从2018年7月开始切换到日历化版本, 目前最新版本: 24.2 (2024-07-28) - [IntelliJ IDEA](https://www.jetbrains.com.cn/en-us/idea/download/other.html) YYYY.MINOR.MICRO, 目前最新版本: 2024.2.3 ## 选择一个合适的版本方案 原则上使用三段式版本号: major.minor.patch, 为了方便比较新旧版本, 每部分只能由数字组成, 建议不填充零。 - Android 应用 android 系统应用有 **VERSION_CODE** 和 **VERSION_NAME** 两个和版本相关的常量值(BuildConfig中)。VERSION_CODE 类型为 int32,它作为系统比较 app 版本新旧的标准。Google Play 允许的最大 `versionCode` 值为 2100000000。所以要合理分配版本中每一部分占用的长度, 见下表: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | | | ---- | --------- | ------------ | ------------ | -------- | ------- | ------- | ------- | ------- | ------- | ------------- | | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | | | **~~2~~** | ~~2~~ | ~~4~~ | *~~9~~* | *~~9~~* | *~~1~~* | *~~2~~* | *~~3~~* | *~~1~~* | **主版本** | | | **2** | **4** | 2 | *2* | *4* | *1* | *2* | *3* | *1* | 次版本 | | | **~~2~~** | **~~4~~** | ~~1~~ | ~~0~~ | ~~0~~ | *~~1~~* | *~~2~~* | *~~3~~* | *~~1~~* | *更新* | 选择表格中第二种方案: YY.MINOR.YY0M0D[-Hmm] 示例: 24.2.241014、24.2.241231-2359、24.2.250101-000 综合 Android VERSION_CODE 的限制, 统一所有项目、代码、工具库的版本格式为: **YY.MINOR.PATCH[-Hmm]** - **YY** 未正式发布时, 不启用, 设置为 0; 正式发布时 YY 为首次发布时的年份后两位 - **MNIOR** 次版本取值为 [1,9], 相当于一年内可以有 9 个主要版本 - **PATCH** 值为编译/打包的日期, 其中的 YY不受第一段(首次发布时的年份)的限制。也就是说,patch 可能发生在发布后的任意时间 - **Hmm** 可选的, 当在一天内有多次更新时, 用来区分不同的更新 - 压缩 **PATCH**: 压缩 patch 就是用更小的值来表示一个日期,patch 的最大值为 991231。年份可以用 相对于 **YY** 的增量来表示, 因为 patch 一定发生在发布之后。patch 的第一段 = patch.YY - YY, 最大值为 99, 99 < 127 = (0111 1111)2 需要 7bit, 月份最大值为 12,12 < 15 = (1111)2 需要 4bit, 日最大值为 31, 31 = (0001 1111)2 需要 5bit。一共需要 7 + 4 + 5 = 16bit,最大值 = 2^16 - 1 = 65535 < 991231。特别地,如果把月份认为从 0 开始。那么在 **YY**年1月1日的 patch = 00 00 01,移除前面无意义的 0,patch 的值刚好是1。更接近计数的方式。所以 patch 和 date 之间的转换方式为: ```javascript // javascript // date => patch function dateToPatch(YY) { let now = new Date() let dY = (now.getFullYear() - YY) % 100 let m = now.getMonth() // js month start from 0 let d = now.getDate() let patch = dY * 512 + m * 32 + d return patch } // patch => date function patchToDate(patch, YY) { let dY = Math.floor(patch / 512) let m = Math.floor((patch - dY * 512) / 32) + 1 let d = patch % 32 let date = (YY + dY) * 10000 + m * 100 + d return date } console.log(patchToDate(1, 0)) // 101, 当年1月1日 console.log(patchToDate(31, 0)) // 131,当年1月31日 console.log(patchToDate(33, 0)) // 201, 当年2月1日 console.log(patchToDate(383, 0)) // 1231,当年12月31日 console.log(patchToDate(513, 0)) // 10101,次年1月1日 console.log(patchToDate(895, 0)) // 11231,次年12月31日 console.log(patchToDate(4991, 0)) // 91231,9年后的12月31日 console.log(patchToDate(5121, 0)) // 100101,10年后的1月1日 console.log(patchToDate(51071, 0)) // 991231,99年后的12月31日 ``` **patch 在当月内是连续的、跨月和跨年是不连续的** 示例和解读: 24.1.1:24年第一版第一次(1月1日)发布 24.2.31:24年第二版1月31日更新 24.2.382:24年第二版12月30日更新 24.2.383-000:24年第二版12月31日0时更新 24.2.513-1030:24年第二版2025年1月1日10时30分更新