Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)
Posted
技术标签:
【中文标题】Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)【英文标题】:Unstable behavior of a library (UnsatisfiedLinkError) in Eclipse/Gradle multi-projects 【发布时间】:2021-09-28 15:09:53 【问题描述】:最初有 2 个 Gradle 子项目。主要的“app”和“lib”服务。 在“app”中有一个资源目录,其中托管了一个自定义库(库直接构建在此目录中,以便立即集成到项目中)。但有一次,库的扩展导致签名错误,在寻找解决方案时,我尝试了一个提议(在网上找到),其中包括调用 Xcode 的“Clean Build Folder”功能。签名问题尚未解决,但“app”的所有来源已被删除。
在这次冒险之后,我决定创建第三个 Gradle 子项目(“oslib”),它只包含库和承载 JNI 的 Java 代码。至少如果源头移除再次发生,修复成本会比主项目低得多。
一旦签名问题得到解决,库就能够继续发展并正常进行测试,直到发生系统错误(崩溃)。没有什么异常,一个以自己的方式表现出来的库错误。
除了从此刻开始(不更改 Java 环境中的任何内容)之外,任何访问该库的尝试都会在调用的本机 Java 方法上导致 UnsatisfiedLinkError。 库加载没有错误,但行为与缺少库的行为相同。 我不明白为什么现在会有这种行为变化。为了看看会发生什么,我用最简单的代码重现了这个问题,而出现的问题让我更加困惑:
文件«AppLib.h»
#import <Foundation/Foundation.h>
#import <simd/simd.h>
#import <jni.h>
#ifndef _Included_AppLib
#define _Included_AppLib
#ifdef __cplusplus
extern "C"
#endif
JNIEXPORT void JNICALL Java_test_TestJni_testNative (JNIEnv *env, jobject obj);
#ifdef __cplusplus
#endif
#endif
文件«AppLib.m»
#import <simd/simd.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSWindow.h>
#import "AppLib.h"
JNIEXPORT void JNICALL Java_test_TestJni_testNative (JNIEnv *env, jobject obj)
NSLog(@"CALL Java_test_TestJni_testNative");
这里是通过“oslib”子项目的 JNI 访问类的代码,该子项目在其资源目录中包含该库:
package test;
public class MainClassProj3
public static void main(String[] args)
TestJni.test();
这里,在“loadLibrary()”方法中,库被复制到一个临时目录中(以便以后从一个 jar 文件中工作),然后从这个目录中加载。在评论中将有可能直接加载库(这对继续很重要)。
package test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class LibraryLoader
public static void loadLibrary(final String libPath, final String libName)
try
final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
final String outPath = System.getProperty("java.io.tmpdir") + libName;
final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
final OutputStream out = new FileOutputStream(new File(outPath));
in.transferTo(out);
in.close();
out.close();
System.out.println(outPath);
System.load(outPath);
// System.load("/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib");
catch (Exception e)
e.printStackTrace();
这里是与库建立链接的类。
package test;
public class TestJni
static
LibraryLoader.loadLibrary("", "libAppLib.dylib");
public native static void testNative();
public static void test()
testNative();
这里是“app”子项目的主类的代码,它访问“oslib”子项目的服务(对native方法的调用)。
package test;
public class MainClassProj1
public static void main(String[] args)
TestJni.test();
如果我从第一个项目(“app”子项目)的主类调用库,则会收到错误消息。 该库似乎可以正确加载(没有例外),但访问本机方法的行为就像未加载该库一样:
/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
at test.TestJni.testNative(Native Method)
at test.TestJni.test(TestJni.java:12)
at test.MainClassProj1.main(MainClassProj1.java:6)
如果我从第三个项目(“oslib”子项目)的主项目调用库,则操作是正确的:
/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
2021-07-19 19:39:38.788 java[14357:530171] CALL Java_test_TestJni_testNative
如果我再次尝试第一个项目,错误仍然存在。
/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
at test.TestJni.testNative(Native Method)
at test.TestJni.test(TestJni.java:12)
at test.MainClassProj1.main(MainClassProj1.java:6)
每次调用 LibraryLoader.loadLibrary(String, String) 时,库都会被复制到临时目录中,并且永远不会被删除。因此,自从第一次测试以来,它就出现在“/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T”中。 所以通过替换LibraryLoader.loadLibrary(String, String)方法的内容:
public class LibraryLoader
public static void loadLibrary(final String libPath, final String libName)
try
// final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
// final String outPath = System.getProperty("java.io.tmpdir") + libName;
// final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
// final OutputStream out = new FileOutputStream(new File(outPath));
//
// in.transferTo(out);
// in.close();
// out.close();
//
// System.out.println(outPath);
// System.load(outPath);
System.load("/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib");
catch (Exception e)
e.printStackTrace();
如果我再次运行第一个项目,错误仍然存在。
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
at test.TestJni.testNative(Native Method)
at test.TestJni.test(TestJni.java:12)
at test.MainClassProj1.main(MainClassProj1.java:6)
如果我在第三个项目之后立即运行,则会发生错误(新行为)。
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'void test.TestJni.testNative()'
at test.TestJni.testNative(Native Method)
at test.TestJni.test(TestJni.java:12)
at test.MainClassProj3.main(MainClassProj3.java:6)
现在我再次更改 LibraryLoader.loadLibrary(String, String) 的内容以返回其初始版本(在加载之前将库复制到临时目录中)然后我只运行第三个项目。一切都很好:
/var/folders/4t/wfx1sb3d2b7d82x56zmr3l1h0000gn/T/libAppLib.dylib
2021-07-19 20:03:30.308 java[19075:553720] CALL Java_test_TestJni_testNative
我再次更改 LibraryLoader.loadLibrary (String, String) 的内容以返回其没有库副本的版本,然后我只运行第三个项目。 这次没有报错:
2021-07-19 20:06:55.747 java[19711:557089] CALL Java_test_TestJni_testNative
现在我再次运行第一个项目,这次也没有错误。
2021-07-19 20:10:05.934 java[20337:560000] CALL Java_test_TestJni_testNative
如果我按顺序重复刚才描述的内容,我会得到完全相同的结果。 请注意,从 jar 文件运行此代码可以完美运行。
有人对这种行为有解释吗?
访问只读文件(“oslib”子项目的资源目录中的库在任何时候都没有修改的库)的一系列独立执行如何导致或解决不稳定?
Mac OS 版本为 10.15.2 Java 版本为 OpenJDK 15.0.3 XCode 版本为 11.7 Eclipse 版本为 2021-06 (4.20.0)
【问题讨论】:
【参考方案1】:我有一个我不太喜欢的解决方法,但它解除了我的障碍,其中包括识别我的开发环境并直接在其资源目录中查找库。希望它保持稳定。
在“LibraryLoader.loadLibrary()”方法中:
public class LibraryLoader
public static void loadLibrary(final String libPath, final String libName)
try
final String appPath = new File(System.getProperty("user.dir")).toString();
final String srcPath = (((libPath == null) || libPath.isEmpty()) ? "" : ("/" + libPath)) + "/" + libName;
final String outPath = System.getProperty("java.io.tmpdir") + libName;
final InputStream in = LibraryLoader.class.getResourceAsStream(srcPath);
final OutputStream out = new FileOutputStream(new File(outPath));
final String devPath;
if (appPath.startsWith("/Users/ ... absolute path of the main project ... /app"))
devPath = appPath.replaceAll("app", "oslib/src/main/ ... path where the library is located ... /" + srcPath);
System.load(devPath);
else
in.transferTo(out);
in.close();
out.close();
System.out.println(outPath);
System.load(outPath);
catch (Exception e)
...
【讨论】:
以上是关于Eclipse/Gradle 多项目中库的不稳定行为 (UnsatisfiedLinkError)的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio 中库的 NoClassDefFoundError [重复]
怎么百变android studio 中库的arr文件的输出目录呢?
Eclipse Gradle 向导使用 SubFolder -lib 创建项目
导入eclipse gradle spring boot项目后,Lombok无法在intellij idea 18.1版本中工作