使用Android NDK编译依赖其他本地库的本地库运行,应该怎么做?

8
我有一个大型的C文件库,依赖于OpenSSL,我对使用本地库非常陌生,尤其是对Android的处理非常陌生。我已经成功在iOS上实现了这个库,没有任何问题。
我一直在网上寻找关于如何做到这一点的教程/信息,但到目前为止,我还没有找到我能理解的任何东西。对我来说,这一切都非常混乱。
基本上,我试图弄清楚如何使用NDK将C文件库编译成本机库,并在我的Android应用程序中使用编译后的库。
据我所知,我需要一个Android自定义cmake文件,但我不知道从哪里开始,而且我找到的文档非常难以理解。
因此,我需要一些帮助理解编译库的过程:
1. 使用Gradle和CMakeLists.txt编译库,将其链接到openssl和libcrypto,并将最终库与我的Android Studio项目链接。 2. 能够从我的Java代码中调用这些库中的本机函数。(我知道这需要JNI Java包装器)
(我已经成功构建了所有android需要的archs的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) 替换最后一行。其他修复已在我的更新答案中。 - Alex Cohn
1
@Stargateur 我正在尝试编译和链接的库是用C语言编写的,最终使用的编译器是GCC的衍生版本。我要链接到我的库中的库也是用C语言编写的。我的JNI包装器也将使用C语言编写。为什么我不应该添加C标签呢? - Nathan F.
1
@NathanFiscaletti,C语言本身就是一门编程语言。如果你遇到与链接器相关的问题,那与C语言本身无关。按照你的逻辑,我们应该为所有使用C语言编写的操作系统(如Linux、Windows等)添加C标签,这很荒谬。 - Stargateur
你可能会对 libcrypto.solibssl.so 感到非常失望。Android 的 init 进程 Zygote 会预加载一个旧版本的 OpenSSL 库,该库已经捆绑在操作系统中。您打包的更新版本将永远不会被加载。您应该使用 OpenSSL 维基上讨论的 Wrapper Shared Object 技术。 - jww
我建议我们分开讨论构建适用于所有Android架构的最新OpenSSL的问题。这是一个单独的问题,并需要不同的专业知识。@jww对包装器的评论需要额外的研究。但这与编译依赖其他本地库的本地库关系不大。 - Alex Cohn
显示剩余3条评论
3个回答

7
更新: 对于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通常足够)。你可以找到官方教程冗长乏味。在这种情况下,您可以在GitHub或其他地方找到预构建的二进制文件。决定是否想要共享库或静态库。

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

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

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

工作的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)

以下是几点澄清:

  • LOCAL_SRC_FILES的路径是相对于LOCAL_PATH的。
  • LOCAL_C_INCLUDES的路径是相对于工作目录的,这就是为什么我们经常在前面加上$(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"

如果您在Android Studio 2.3或更高版本中使用gradle插件构建库,则会忽略APP_ABI设置。您必须在abiFilters中指定列表,如此处所示。


我已经按照你的建议进行了更改,但是现在当我运行ndk-build时出现了一个错误:libcrypto.a(ui_openssl.o):ui_openssl.c:function read_string_inner: error: undefined reference to 'signal'。我有一种感觉这与我的openssl构建有关。但是我不知道该如何正确编译它,尽管我认为我已经做过了。 - Nathan F.
你按照我上面的答案进行了Android教程吗?还是你从互联网下载了预编译的libcrypto? - Alex Cohn
我没有下载预编译版本。然而,你在官方Android教程中提供的那个脚本已经过时了。我无法使用它来为当前Android支持的所有7种活动架构进行编译。 - Nathan F.
而且最新版本的openssl甚至无法在arm架构上运行。 - Nathan F.
好的。但是如果你想要帮助找出构建过程中出了什么问题,请解释一下你是如何构建这些库的。 - Alex Cohn
显示剩余5条评论

4
有成千上万种本地开发(我指的是C/C++)可以进行的操作。这些语言非常轻便,你可以将一些重型操作从Java转移到C/C++中。例如,相机硬件是Android设备中最重且最糟糕的硬件之一。因此,当你尝试处理它并添加自己的操作时,情况真的很糟糕。当涉及到本地开发时,这些操作在CPU上工作,不需要通过Dalvik或其他方式进行翻译。这就是为什么你需要所有Android设备的CPU架构。因为每个架构都有自己的计算速度或技术。如果有任何协处理器,你也应该考虑到这一点。这是关于NDK的简短概述。
对于构建或重建本地库,你有两种方法,CMake和NDK构建。这两种构建系统之间的差异不大,因此我想用NDK构建来解释我的答案。
对于本地开发,你应该有一个主/ 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)

--- 第一行返回主/本地代码路径。

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

--- 第三行为每个在其中声明的架构构建模块。

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

--- 第五行添加其他共享库,您需要在本地源中使用它们的源代码。

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

--- 第七行显示您尝试构建的C ++源文件。

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

在Application.mk中,您可以像在发布模式或调试模式下一样给出命令。您应该在此文件中提供架构。例如:APP_ABI:= armeabi-v7a x86 arm64-v8a x86_64等。

当您尝试构建库并在Java端使用它们时,您需要执行一些操作。

  1. Check your ndk path in local.properties file in project folder. It shows ndk-bundle path for building makefiles.

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

  2. Check build scripts in gradle file. This script shows where shared libs should be located:

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }
    
  3. And show your makefile path to gradle.

    externalNativeBuild {
       ndkBuild {
          path 'src/main/jni/Android.mk'
       }
    }
    
我认为我的答案将有助于您了解一些细节。另外,您还可以查看这些答案。
1. 调用本机方法:点击这里 2. 链接共享库:点击这里 祝你愉快!

0

首先,您需要已经交叉编译(针对您的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。希望这可以帮助您。


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