目录
背景
记录一下在使用Java开发串口调试工具的时候发现,将程序打包成可以在无jre环境运行exe程序的时候,发现的一个问题并且解决。
我遇到的问题是由于使用RXT串口通讯包里面需要复制 rxtxParallel.dll 和 rxtxSerial.dll 到 C:\Program Files\Java\jdk1.8.0_241\jre\bin 下,而使用Inno Setup Compiler工具打包的jre环境就是在C:\Program Files\Java\jdk1.8.0_241\jre这个文件夹。
打包完毕后再无jre环境下能够正常安装程序,但是在电脑本身已经存在jre环境的情况下,程序会默认使用系统已经配置的jre进行程序执行。从而导致打包的程序无法运行,因为找不到rxtxParallel.dll 和 rxtxSerial.dll这俩个可执行文件。
涉及到的打包工具、串口通讯工具和包
以下是我开发的时候参考的帖子和工具
解决
当我们在有jre环境,但是jre\bin文件夹下并没有 rxtxParallel.dll 和 rxtxSerial.dll 这俩个可执行文件的时候,会出现以下报错:
java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in Application init method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application init method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:912)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:123)
at com.lzq.utils.SerialPortUtils.listPorts(SerialPortUtils.java:17)
at com.lzq.gui.OperationInterfaceView.init(OperationInterfaceView.java:71)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:841)
... 2 more
Exception running application com.lzq.gui.OperationInterfaceView
报错的内容显示找不到rxtxSerial可执行文件,这种情况就是加载器没有加载到,但是代码中需要调用rxtxSerial,就会出现报错。
解决的思路是我需要手动将rxtxParallel.dll 和 rxtxSerial.dll俩个可执行文件进行加载,我在程序中添加上此方法:
loadLibrary方法:
/**
* 加载指定名称的本地库。
*
* @param libraryName 本地库的名称(例如 "rxtxSerial.dll")
*/
public void loadLibrary(String libraryName) {
// 获取当前工作目录
String currentDirectory = System.getProperty("user.dir");
// 构建本地库所在路径(假设在 jre/bin 目录下)
String libraryPath = currentDirectory + File.separator + "jre" + File.separator + "bin";
// 构建本地库文件的完整路径
String libraryFullPath = libraryPath + File.separator + libraryName;
// 加载本地库文件
System.load(libraryFullPath);
}
并且在主程序入口调用进行加载
this.loadLibrary("rxtxSerial.dll");
this.loadLibrary("rxtxParallel.dll");
这样就解决了已经存在jre环境但是运行会出现报错,出现没有加载rxtxParallel.dll 和 rxtxSerial.dll 情况。
但是这样的代码会出现一种情况,就是他会主动去加载rxtxParallel.dll 和 rxtxSerial.dll。但是在默认没有jre环境的情况下,使用的是我们手动打包的jre环境去加载,这个环境是已经存在rxtxParallel.dll 和 rxtxSerial.dll这俩个可执行文件的。
这种情况就会出现重复加载的问题。会出现报错:
java.lang.RuntimeException: Exception in Application init method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:912)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsatisfiedLinkError: Native Library C:\Program Files (x86)\机器操作工具\jre\bin\rxtxSerial.dll already loaded in another classloader
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1907)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1845)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:123)
at com.lzq.utils.SerialPortUtils.listPorts(SerialPortUtils.java:17)
at com.lzq.gui.OperationInterfaceView.init(OperationInterfaceView.java:75)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:841)
... 2 more
报错的内容显示由于尝试加载已经在另一个类加载器中加载的本地库文件而引起的。
解决的思路就是,在执行加载之前先做一个判断,判断是否需要手动加载,是已经存在。
isJREAvailable方法:
/**
* 检查当前环境是否已安装 Java 运行时环境(JRE)。
*
* @return 如果已安装 JRE,则返回 true;否则返回 false。
*/
public boolean isJREAvailable() {
// 获取 Java 安装目录路径
String javaHome = System.getProperty("java.home");
// 构建 JRE 的 bin 目录路径
File jreBinDirectory = new File(javaHome, "bin");
// 检查 bin 目录是否存在
return jreBinDirectory.exists();
}
isLibraryLoaded方法:
/**
* 判断是否加载了(libraryName)文件
* @param libraryName
* @return
*/
public boolean isLibraryLoaded(String libraryName) {
// 获取本地库加载路径
String libraryPath = System.getProperty("java.home");
// 构建本地库文件的完整路径
String libraryFilePath = libraryPath + File.separator + "bin" + File.separator + libraryName;
// 检查本地库文件是否存在
File libraryFile = new File(libraryFilePath);
// 如果本地库文件存在,则返回 true(表示库已加载),否则返回 false
return libraryFile.exists();
}
修改后的loadLibrary方法:
/**
* 加载指定的本地库文件。
*
* @param libraryName 要加载的本地库文件名
*/
public void loadLibrary(String libraryName) {
// 判断是否有JRE环境和是否已加载指定的库文件
if (isJREAvailable() && !isLibraryLoaded(libraryName)) {
// 获取当前工作目录
String currentDirectory = System.getProperty("user.dir");
// 构建JRE的bin目录路径
String libraryPath = currentDirectory + File.separator + "jre" + File.separator + "bin";
// 构建库文件的完整路径
String libraryFullPath = libraryPath + File.separator + libraryName;
// 加载库文件
System.load(libraryFullPath);
}
}
并且在主程序入口调用进行加载
this.loadLibrary("rxtxSerial.dll");
this.loadLibrary("rxtxParallel.dll");
至此,就解决了java程序在打包后的exe程序在不同环境下的问题。