Android NDK/JNI: 构建依赖其他共享库的共享库

18

我正在编写一个Android应用程序,想要调用使用NDK构建的共享库中的JNI函数。难点在于该共享库会调用由其他共享库提供的函数。这些其他共享库是在别处编译的C库。

以下是我的尝试:

我的环境: 我正在使用Eclipse开发。我添加了本机支持并拥有一个jni库。在该库中,我有自己的代码和一个\lib目录,在那里我复制了其他的.so文件。

尝试 #1 Android.mk:只是告诉它库的位置

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib1
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib2

include $(BUILD_SHARED_LIBRARY)

这个构建过程很顺利,但是当我尝试运行时,出现了错误,指示dlopen(libnative_lib)失败,因为它不能载入libsupport_lib1。

在这里我找到了这个:

Can shared library call another shared library?

其中提到需要对所有必要的库进行load library调用。太好了!

尝试 #2 先打开每个库

static {
    System.loadLibrary("support_lib1");
    System.loadLibrary("support_lib2");
    System.loadLibrary("native_lib");
}

再次构建是没有问题的,但运行时出现了新的错误:

无法加载libsupport_lib1库。findLibrary返回null。

现在我们有所进展了。目标机可能没有加载库文件。

尝试 #3:将 .so 文件复制到项目/libs/armeabi 目录下

这样不行,在 Eclipse 构建时,会删除我放进去的文件。

尝试 #4:为每个库创建一个新模块

于是我找到了这篇文章:

Android NDK:使用预编译静态库进行链接

虽然这是关于静态库的,但我可能遇到了类似的问题。大意是说我需要为每个库声明一个模块。因此我的新 Android.mk 文件看起来像这样:

LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib1.so
include $(BUILD_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib2.so
include $(BUILD_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib1
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib2

include $(BUILD_SHARED_LIBRARY)

这个构建成功了!更好的是,armeabi现在有了sos!更好的是当我尝试运行它时,我收到以下消息(告诉我支持库1和2由LoadLibrary打开):

正在尝试加载lib /data/app-lib/com.example.tst/libsupport_lib1.so 添加共享库/data/app-lib/com.example.tst/libsupport_lib1.so 在/data/app-lib/com.example.tst/libsupport_lib1.so中未找到JNI_OnLoad,跳过初始化

但是…… dlopen失败:无法定位由libnative_lib.so引用的libsupport_lib.so中存在的函数func_that_exists_in_libsupport_lib.so符号

编辑:第5次尝试:使用PREBUILT_SHARED_LIBRARY

于是我找到了这个: 如何将预构建的共享库链接到Android NDK项目?

这似乎正是我要问的。他们的答案似乎是“不要使用'build_shared_library'而是使用'PREBUILT_SHARED_LIBRARY'

好的,让我们试试吧。

 LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

构建失败!现在构建抱怨缺少符号。

编辑:尝试6:压平一切

所以我回到了NDK中的预构建文档。它说:

每个预构建库都必须被声明为一个独立的模块,以便于构建系统。以下是一个微不足道的示例,假设文件“libfoo.so”位于与下面的Android.mk相同的目录中:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
include $(PREBUILT_SHARED_LIBRARY)

请注意,要声明这样的模块,您实际上只需要以下内容:

为模块命名(这里是“foo-prebuilt”)。这不需要与预构建库本身的名称相对应。

将LOCAL_SRC_FILES分配给您提供的预构建库的路径。与往常一样,路径相对于您的LOCAL_PATH。

如果您提供的是共享库,请使用PREBUILT_SHARED_LIBRARY而不是BUILD_SHARED_LIBRARY。对于静态库,请使用PREBUILT_STATIC_LIBRARY。 预构建模块不会构建任何内容。但是,您的预构建共享库的副本将被复制到$PROJECT/obj/local,并且另一个副本将被复制并剥离到$PROJECT/libs/。

因此,让我们尝试将所有内容展平以匹配简单的示例。我将我的库从舒适的/lib文件夹中复制出来,放在jni根目录下。然后我做了这件事:

 LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := support_lib1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := support_lib2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

同时出现相同错误。此外,我绝对没有看到库文件被复制到$PROJECT/obj/local。

那现在该怎么办?


一个线索!Eclipse放入我的armeabi文件夹中的库并不是我试图提供的那些的副本。在armeabi文件夹中的libsupport_lib1.so和jni/lib中的libsupport_lib1.so大小不一样...那么我做错了什么,没有将其复制到正确的位置呢? - djc6535
非常不幸的是,库不能直接放在libs/中,否则Eclipse会在构建时将它们删除..这样会更简单..我无法找到任何解决方法。 - TTimo
你好,我也遇到了同样的问题。你解决了吗?如果是这样,能否给我你的解决方案? - DreamInBox
3个回答

15

你的问题在于命名规则。NDK和Android要求共享库名称始终以lib开头。否则,库将无法正确链接,并且不会正确复制到libs / armeabi文件夹中,也不会安装在设备上(不会正确复制到 /data/data/package/lib目录下)。

如果你将support_lib1.so重命名为libsupport_1.so,将support_lib2.so重命名为libsupport_2.so,并将这两个文件放入jni/lib目录中,则你的尝试 #5只需要进行微小的更改就能正常工作:

LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := lib/libsupport_1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := lib/libsupport_2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

顺带一提,我认为你不需要这个-L$(SYSROOT)/../usr/lib

另外,别忘了也要更新Java端:

static {
    System.loadLibrary("support_lib1");
    System.loadLibrary("support_lib2");
    System.loadLibrary("native_lib");
}

3
现在Gradle提供了实验性插件,我们可以在build.gradle文件中配置所有内容。你能提供一个上述Android.mk文件的build.gradle变体吗? - support_ms
到目前为止,我还没有理解如何设置它,以便它使用正确的.so 平台目标。它只允许您选择一个 .so,然后将其分发到所有 armeabi、armeabi-v7a、arm64-v8a 等。如果我们包含的源静态库已经被分成这些平台,我们该如何传递它们? - Jay Snayder
@JaySnayder:没有自动机制将共享库按android-xx拆分,但这很少需要,因为通常情况下,NDK是向后兼容的。这意味着,在Gingerbread上运行的库仍将在Nogut上正常工作。虽然有一些边缘情况,但当您将APK拆分为不同目标SDK版本时,提供不同的**.so**变体是有道理的。例如,您不需要armeabi用于Lolliop及更高版本。 - Alex Cohn

5

不确定您是否需要这些内容,但以下是我对此类事情的了解。

  1. 将每个预构建库制作成独立的Makefile。在Android.mk中使用多个目标会变得混乱。很遗憾。
  2. 使用$(call import-add-path)$(call import-module)包含每个make文件。
  3. 尽可能从预构建的make文件中导出内容,使用LOCAL_EXPORT_系列变量。

预构建共享库 Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := my_module_name

MY_LIBRARY_NAME := shared_library_name

### export include path
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include

### path to library
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/lib$(MY_LIBRARY_NAME).so

### export dependency on the library
LOCAL_EXPORT_LDLIBS := -L$(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/
LOCAL_EXPORT_LDLIBS += -l$(MY_LIBRARY_NAME)

include $(PREBUILT_SHARED_LIBRARY)

假设预构建库位于以下目录结构中:
+ SharedProjectFolderName
+--- Android.mk
+--- include/
+-+- libs/$(TARGET_ARCH_ABI)/
  |- libshared_library_name.so

如果您不需要为多个ABI构建,我想您可以省略那一部分。
项目的Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := my_jni_module

## source files here, etc...

### define dependency on the other library
LOCAL_SHARED_LIBRARIES := my_module_name

include $(BUILD_SHARED_LIBRARY)

$(call import-add-path,$(LOCAL_PATH)/path/to/myLibraries/)
$(call import-module,SharedProjectFolderName)
$(call import-module,AnotherSharedProject)

我建议您将所有共享库放在一个文件夹中。当您使用 $(call import-module,SharedProjectFolderName) 时,它会查找包含一个名为 Android.mk 的文件夹,该文件夹在您指定的搜索路径 (import-add-path) 中。

另外,您可能不应该指定 LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib。它应该自己从 NDK 找到正确的库。添加更多的链接器路径可能会让它困惑。正确的方法是从子模块导出链接器路径作为标志。

此外,您可以使用 ndk-build V=1 来获取大量信息,以了解为什么找不到路径等问题。


我正在尝试您的解决方案,但是出现了错误:“在导入路径中找不到标记为'AnalyzerPrebuild'的模块”。请问SharedProjectFolderName相对于主项目位于哪里? - Asaf Pinhassi
您可以使用额外的 $(call import-add-path,....) 指令添加多个路径。所有模块都必须能够通过这种方式找到。 - yano

0

-L选项为链接器提供了一个目录路径,以便查找库文件。-l选项为链接器提供了要链接的库文件名。库文件名必须以“lib”开头。您的库应该命名为libsupport_lib1.so和libsupport_lib2.so。如果您这样做了,那么这可能是您应该执行的操作(替换尝试#1):

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog -lsupport_lib1 -lsupport_lib2
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib

链接器将使用-l指定的库名称前缀添加“lib”,并在其后缀中添加“.so”。 (为什么要使用-L $(SYSROOT)/ .. / usr / lib?)

我认为尝试#1和#2失败是因为您没有将库链接到可执行文件中-它们没有在-l选项中提到。 顺便说一下,您可以自行验证此操作。 解压缩.apk文件并查看lib目录和子目录。 您的.so文件在那里吗?

查看错误:

but then... dlopen failed: Could not locate symbol func_that_exists_in_libsupport_lib.so referenced by libnative_lib.so

你能提供完整的信息吗?dlopen()函数将库加载和链接到运行的进程中。


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