【问题标题】:Compile native library that relies on other native libraries to run using Android NDK?使用Android NDK编译依赖其他原生库运行的原生库?
【发布时间】:2017-11-02 00:05:27
【问题描述】:

我有一个依赖于 OpenSSL 的大型 C 文件库,而且我对使用本机库非常陌生,尤其是对使用 android 的新手。我已经成功地在 iOS 上实现了这个库,没有任何问题。

我一直在网上寻找有关如何执行此操作的教程/信息,但到目前为止我还没有找到任何我能理解的东西。这一切都让我很困惑。

基本上我想弄清楚如何使用 NDK 将 C 文件库编译为本机库,并将编译后的库与 imy android 应用程序一起使用。

据我了解,我需要一个用于 android 本身的自定义 cmake 文件,但我不知道从哪里开始,而且我发现的文档非常难以遵循。

所以,我需要一些帮助来理解在以下庄园中编译库的过程:

  1. 编译库,将其与 openssl 和 libcrypto 链接,使用 Gradle 和 CMakeLists.txt 并将最终库与我的 Android Studio 项目链接。
  2. 能够从我的 Java 代码调用这些库中的本机函数。 (我知道这需要 JNI Java Wrapper)

(我已经设法为 android 需要的所有拱门构建 libssl 和 libcrypto,但我不确定如何处理 .a/.so 文件。)

我们将非常感谢您对此事的任何帮助。

谢谢

编辑:

我设法用以下内容生成了一些库文件。

文件结构:

.idea
app
build
gradle
jni    <--- I made this folder specifically fo this.
    Android.mk <-- This is the important file.
    include
        openssl
            <openssl header files>
    libs
        arm64-v8a
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        armeabi
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        armeabi--v7a
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        mips
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        mips64
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        x86
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
        x86_64
            libcrypto.a
            libcrypto.so
            libssl.a
            libssl.so
    src <--- These are example files that i used to build this with.
        myc_files.c
        myother_c_files.c
        myc_heades.h
        myother_c_headers.h

然后我在 Android.mk 中使用以下内容通过运行 ndk-build 生成一些库。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# Select from arm64-v8a armeabi armeabi-v7a mips mips64 x86 x86_64
ARCH := armeabi

APP_UNIFIED_HEADERS := true

#APP_ABI := $(ARCH)
# TARGET_ARCH := $(ARCH)
TARGET_PLATFORM := android-14

SRC_DIR := $(LOCAL_PATH)/src
HDR_DIR := $(LOCAL_PATH)/include
LIB_DIR := $(LOCAL_PATH)/libs

LOCAL_C_INCLUDES := $(HDR_DIR)
LOCAL_MODULE := myclib
LOCAL_SRC_FILES := $(SRC_DIR)/myc_files.c $(SRC_DIR)/myother_c_files.c
LOCAL_LDLIBS += -L$(LIB_DIR)/$(ARCH) -lssl -lcrypto

include $(BUILD_STATIC_LIBRARY)

这些库被放入PROJECT/obj/local 并且我不知道从这里去哪里,或者该 Android.mk 文件是否确实正确构建了这些库。

【问题讨论】:

  • 您必须将库构建为共享库,因此将最后一行替换为 include $(BUILD_SHARED_LIBRARY)。其他修复在我更新的答案中。
  • 为什么要加C标签?
  • @Stargateur 我正在尝试编译和链接的库是用 C 语言编写的,最终使用的编译器是 GCC 的衍生产品。我链接到我的库的库是用 C 编写的。我的 JNI 包装器将在 C 中。我为什么不添加 C 标记?
  • @NathanFiscaletti C 是关于语言的。如果您对链接器有疑问,这与 C 无关。根据您的逻辑,我们应该为所有内容添加 C 标签,因为 linux、windows 等......都是用 C 编写的。所以一切都是关于 C 的,对吗?这很愚蠢。
  • 您可能会对libcrypto.solibssl.so 感到非常失望。 Zygote 是 Android 的 init,将预加载与操作系统打包的 OpenSSL 库的旧版本。您打包的更新版本永远不会加载。您应该使用 OpenSSL wiki 上讨论的 Wrapper Shared Object 技术。

标签: android-ndk openssl android-gradle-plugin


【解决方案1】:

更新:使用 AGP 4.0(仍未发布),您可以从 maven 使用 OpenSSL。

dependencies {
    implementation "com.android.ndk.thirdparty:openssl:1.1.1d-alpha-1"
}

Android.mk 中,您只需添加(在末尾)

$(call import-module, prefab/curl)

这里有完整的细节:https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html


原答案:

  • 0. 为 Android 构建 openssl 库;您必须选择 ABI(armeabi-v7ax86 通常就足够了)。你会发现official tutorial 又长又无聊。在这种情况下,您可以在 GitHub 或其他地方找到预构建的二进制文件。决定你想要共享库还是静态库。

  • 1. 使用 Android Studio 2.3,您可以通过集成的 externalNativeBuild gradle 任务构建您的库。但是您可以使用ndk-build 命令单独构建它。

  • 2.如果您选择不使用 gradle,请将构建的共享库复制到您的 Android 项目的 app/src/main/jniLibs 目录。

  • 3. 您需要一个 JNI 包装器来连接您的 Java 代码和 C 代码。在 Java 方面,在一个或多个类中声明必要的 native 方法。这些方法必须在 C/C++ 端实现,如 Android JNI walkthrough 中所述。您的 Java 代码必须显式加载包含这些本地方法实现的共享库。请注意,您不能直接调用库中或 opensssl 库中的本机函数,即使这些函数已导出

正在运行的 Android.mk

include $(CLEAR_VARS)

LOCAL_MODULE := vrazovpn
LOCAL_SRC_FILES := src/myc_files.c src/myother_c_files.c
LOCAL_STATIC_LIBRARIES := openssl libcrypto
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := openssl
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libssl.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libcrypto
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)

很少有 cmets 来澄清上述内容:

  • LOCAL_SRC_FILES 路径相对于 LOCAL_PATH
  • LOCAL_C_INCLUDE 路径是相对于工作目录的,这就是为什么我们经常在它们前面加上$(LOCAL_PATH)
  • 可以使用 LOCAL_C_INCLUDES 来引用属于我们使用的某个库的标头,但是使用 的强大功能将这些标头与库一起导出会更加安全和简洁LOCAL_EXPORT_C_INCLUDES;还有其他设置可以类似地导出。
  • Android.mk 不应该(也不能)设置目标 ABI;它接收来自ndk-build 的目标ABI。您可以通过在 Application.mk 文件或命令行中列出它们来控制要包含在构建中的 ABI,例如

    ndk-build APP_ABI="x86 armeabi-v7a"

如果您使用 gradle 插件在 Android Studio 2.3 或更高版本中构建您的库,APP_ABI 设置将被忽略。您必须在abiFilters 中指定列表。

【讨论】:

  • 我已经按照您的建议进行了更改,但是现在当我运行 ndk-build: libcrypto.a(ui_openssl.o):ui_openssl.c:function read_string_inner: error: undefined reference to 'signal' 时出现错误。我有一种感觉,这与我的 openssl 构建有关。但是我不知道如何正确编译它,我想我已经知道了。
  • 您是否按照我上面的回答中的 Android 教程进行操作?或者你从网上下载了预建的 libcrypto?
  • 我没有下载预建版本。但是,您在官方 Android 教程中提供的那个脚本已经过时了。我无法为 android 当前支持使用它的所有 7 种活动架构进行编译。
  • 而且最重要的是,即使对于带有最新版本 openssl 的手臂拱门,它似乎也不起作用。
  • 很公平。但是,如果您需要帮助找出您的构建出了什么问题,请说明您是如何构建这些库的。
【解决方案2】:

原生开发(我的意思是 C/C++)可以执行数千种操作。这些语言非常轻巧,您可以将一些繁重的操作从 Java 带到 C/C++。例如,相机硬件是 Android 设备中最重和最糟糕的硬件之一。因此,当您尝试处理此问题并添加自己的操作时,这真的很糟糕。对于原生开发,这些操作在 CPU 上运行,不需要通过 Dalvik 或其他方式进行翻译。这就是为什么你需要安卓设备的所有 CPU 架构。因为,每个拱门都有自己的计算速度或技术。如果有任何协处理器,您也应该考虑到这一点。这是关于 NDK 的简短概述。

对于构建或重新构建本机库,您有两种方法,CMake 和 NDK 构建。这两个构建系统之间没有太多区别,所以我想用 NDK 构建来解释我的答案。

对于原生开发,你应该有 main/jni 文件夹,你应该把你的库和原生资源放在里面,然后在这个文件夹中构建它们。在您创建两个文件之前,Android.mk 和 Application.mk。 Android.mk 用于构建原生库(模块)、添加标志等。您可以在 Android.mk 中构建静态和共享库。当您构建 C 源代码时,它会创建静态库 (.a)。但是这些库不适合在 Java 端使用。您只能将它们用于创建共享库 (.so)。为此,您应该构建您的 C++ 源代码,如果需要,您可以将静态库添加到其中。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

ifneq (,$(filter $(TARGET_ARCH_ABI), armeabi-v7a x86 arm64-v8a x86_64))

LOCAL_MODULE := myLibrary

LOCAL_SHARED_LIBRARIES := cpufeatures opencv_imgproc opencv_core 

LOCAL_C_INCLUDES := $(LOCAL_PATH)/c-files

LOCAL_SRC_FILES := $(LOCAL_PATH)/myJni.cpp

include $(BUILD_SHARED_LIBRARY)

--- 第一行返回 main/jni 路径。

--- 第二行清除所有旧的LOCAL_*** 变量。

--- 在那里声明的每个拱门的第 3 行构建模块。

--- 第 4 行显示模块名称,当您构建它时,NDK 会自动为其添加 lib 和 .so 扩展名(libmyLibrary.so)。

--- 第 5 行添加了其他共享库,您需要在本机源中使用它们的源。

--- 第 6 行将 C 文件添加到您的模块中。

--- 第 7 行显示了您尝试构建的 C++ 源代码。

--- 第 8 行是构建命令。

在 Application.mk 中,您可以给出命令,例如,您的项目处于发布模式或调试模式。你应该在这个文件中给出arch。比如APP_ABI := armeabi-v7a x86 arm64-v8a x86_64等。

当你尝试构建库并在java端使用它们时,你应该做一些操作。

  1. 检查项目文件夹中 local.properties 文件中的 ndk 路径。它显示了构建 makefile 的 ndk-bundle 路径。

    ndk.dir=/Users/../Library/Android/sdk/ndk-bundle

  2. 检查 gradle 文件中的构建脚本。此脚本显示共享库的位置:

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }
    
  3. 并显示你的makefile路径到gradle。

    externalNativeBuild {
       ndkBuild {
          path 'src/main/jni/Android.mk'
       }
    }
    

我想我的回答会帮助你理解一些细节。此外,您还可以查看这些答案。

  1. 调用原生方法:Enter

  2. 用于链接共享库:Enter

享受吧!

【讨论】:

    【解决方案3】:

    首先,您需要已经交叉编译(针对您的 Android 平台和 ABI)OpenSSL 库,或者在您的 AS 项目中编译它。 然后,如果您熟悉 cmake 和 gradle,最简单的方法是,您可以在 https://github.com/googlesamples/android-ndk/tree/master/hello-libs 找到您想要的完整示例。 请特别注意 cmake 脚本 CMakeLists.txt,以及 gradle 脚本 app/build.gradle 和 gen-libs/build.gradle。希望对你有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多