在Android JUnit测试中加载本地库

26

我使用ndk-build生成了一个本地库,我能够在我的Android应用程序中加载和使用它。然而,我想对我的应用程序的这一部分编写一些测试。

在我的测试中调用本地函数时,我收到了以下异常消息:

java.lang.UnsatisfiedLinkError: no process in java.library.path

......其中process是我要导入的本地库,名为libprocess.so

我正在使用Roboelectric进行测试,并使用RobolectricTestRunner来运行这个特定的测试,如果有什么区别的话。

如何让我的测试项目看到这个本地库?


编辑: 我正在像这样在我的应用程序中加载库:

static {
    System.loadLibrary("process");
}
public static native int[] process(double[][] data);

在应用程序中调用Process.process(array)正常工作(库已加载),但在测试运行时出现上述异常。


编辑2: 如果我将-Djava.library.path="<libprocess.so所在目录>"设置为VM参数,则:

System.out.println(System.getProperty("java.library.path"));

路径显示是我设置的,但我仍然得到相同的异常。我将目录设置为:

<project-name>/libs/x86

...但作为绝对路径。


你的包名是否正确?你是如何加载库的? - Sam R.
我已经更新了我的问题,请参见上方。 - blork
你能使用这个进行测试吗:-Djava.library.path="<libprocess.so文件所在目录>" - Sam R.
你使用任何代码库吗?有没有可能让我看看你的代码? - Sam R.
抱歉,我无法提供源代码。 - blork
显示剩余5条评论
4个回答

19

对于仍在寻找答案的人,blork有正确的想法——您需要为自己的“本机”平台(Windows、Linux、Mac)编译本机库。Android NDK会为Android平台构建库(.so文件,也可能适用于Linux),这就是为什么在Activity测试用例中运行没有问题(因为它加载了一个Android实例)。

要运行低级别的超级快速的JUnit测试,您需要支持您的JVM。在Windows上,这可能需要构建DLL,在Apple上,需要构建dylibs(假设共享库)。

我刚刚在我的android-ndk-swig-example存储库中完成了一个示例 (https://github.com/sureshjoshi/android-ndk-swig-example/issues/9)。

基本上,在我的CMakeLists中,我加了一个Apple警告:

# Need to create the .dylib and .jnilib files in order to run JUnit tests
if (APPLE)
    # Ensure jni.h is found
    find_package(JNI REQUIRED)
    include_directories(${JAVA_INCLUDE_PATH})

然后我确保Gradle在单元测试中运行,但使用Mac构建系统(而不是NDK)。

def osxDir = projectDir.absolutePath + '/.externalNativeBuild/cmake/debug/osx/'

task createBuildDir() {
    def folder = new File(osxDir)
    if (!folder.exists()) {
        folder.mkdirs()
    }
}

task runCMake(type: Exec) {
    dependsOn createBuildDir
    workingDir osxDir // Jump to future build directory
    commandLine '/usr/local/bin/cmake' // Path from HomeBrew installation
    args '../../../../' // Relative path for out-of-source builds
}

task runMake(type: Exec) {
    dependsOn runCMake
    workingDir osxDir
    commandLine 'make'
}

 project.afterEvaluate {
    // Not sure how much of a hack this is - but it allows CMake/SWIG to run before Android Studio
    // complains about missing generated files
    // TODO: Probably need a release hook too?
    javaPreCompileDebug.dependsOn externalNativeBuildDebug
    if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
        javaPreCompileDebugAndroidTest.dependsOn runMake
    }
 }

注意!

当你使用这种方法时,你实际上并没有测试NDK生成的库。你测试的是相同的代码,但是使用不同的编译器(msvc、xcode、gcc、clang等)进行编译的结果。

从实际意义上讲,大多数测试结果都是有效的——除非你遇到由每个编译器的怪癖或STL实现引起的问题等。虽然这比10多年前要好,但是你不能以100%的确定性说使用主机库进行JUnit测试的结果与Android库完全相同。不过,你可以说它们是相当接近的。

另外,除非你为每个支持的架构使用Android NDK运行本机单元测试,否则你也无法对确定性做任何声明... 所以你需要自己权衡取舍。

一种过度的方法(但如果自动化会很酷)是按照你的方式编写本机单元测试(Google Test、Catch等),然后使用Android NDK在每个架构上编译和运行你的本机库和单元测试。这将提供你跨潜在目标架构的C/C++覆盖率。

从这里开始,你可以使用前面提到的主机库与JUnit快速单元测试JNI层与本机库的交互。在CI系统中,你仍然可能要运行这些相同的单元测试,但作为Android Instrumentation测试(或其他运行模拟Android环境的测试)。

像所有接口一样,你都可以创建mocks——但在某个时候,你还需要系统/功能/集成测试。

更新:

在博客文章中提供了以上内容的更全面解释(http://www.sureshjoshi.com/mobile/android-junit-native-libraries/)。


2
我需要做哪些更改才能在 Windows 上运行(64 位)? - Pregunton
@Pregunton 嗯,也许大部分的构建脚本都是基于*nix风格的参数和目录结构设置的。你需要在PATH中安装cmake,然后设置正确的Windows命令行调用。我相信有一种跨平台的方法可以做到这一点。CMakeLists可能需要进行一些微调,但我只是记不清楚了,因为我是几年前做的。 - SJoshi
我喜欢这个答案,想要加上一个更正。 Android NDK 为 Android 平台构建库(.so 文件 - 可能也适用于 Linux),
这是不正确的,因为 .so 是纯平台特定的,即 x86 或 ARM 或其他平台。
- siva
@SJoshi,您能否请看一下这个链接 https://stackoverflow.com/questions/72238895/cmake-building-dylib-to-test-android-junit-tests - Vivek Mangal

1
我已经切换到默认测试样式(使用ActivityUnitTestCase而不是RoboElectric),现在它可以正常运行。很遗憾我必须牺牲测试运行速度,但在模拟器上运行测试确实有效。您还可以为JNI类创建一个影子类,如此处所述:

Robolectric tanks on Application objects that load JNI libraries. Can I get a workaround?

也许为我的机器编译库会起作用,但我不能再花更多时间在上面了。

1

以防万一,如果还有其他人在寻找解决方案,这里确实有一种方法可以正确地设置JUnit测试的库查找路径(尽管我仍然无法成功运行测试,因为出现了不同的问题):

需要将路径放入LD_LIBRARY_PATH(或Windows上的PATH)环境变量中。我有完全相同的经历:我首先设置了java.library.path,但似乎它并没有以任何方式影响本地加载器的行为。

如果您需要了解如何使用Gradle / Android Studio进行操作的详细信息,请查看我的答案


0
将以下行添加到CMakeLists.txt对我有效。
set(JAVA_AWT_LIBRARY NotNeeded)
set(JAVA_JVM_LIBRARY NotNeeded)
set(JAVA_INCLUDE_PATH2 NotNeeded)
set(JAVA_AWT_INCLUDE_PATH NotNeeded)
find_package(JNI REQUIRED)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接