Android NDK入门

https://developer.android.google.cn/ndk/guides?hl=Language

https://developer.android.google.cn/studio/projects/add-native-code

https://developer.android.google.cn/ndk/guides/cpp-support#system

https://blog.csdn.net/carson_ho/article/details/73250163

 

一、NDK

Android NDK 是一组能将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具。能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用:

在平台之间移植其应用。
重复使用现有库,或者提供其自己的库供重复使用。
在某些情况下提高性能,特别是像游戏这种计算密集型应用。

定义:Native Development Kit,是 Android的一个工具开发包
NDK是属于 Android 的,与Java并无直接关系

作用:快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK
即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互

应用场景:在Android的场景下 使用JNI
即 Android开发的功能需要本地代码(C/C++)实现

特点:

示意图注意:

示意图

 

二、JNI

定义:Java Native Interface,即 Java本地接口

作用: 使得Java 与 本地其他类型语言(如C、C++)交互
即在 Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码

注意:

JNI是 Java 调用 Native 语言的一种特性

JNI 是属于 Java 的,与 Android 无直接关系

步骤:

  1. 在Java中声明Native方法(即需要调用的本地方法)
  2. 编译上述 Java源文件javac(得到 .class文件)
  3. 通过 javah 命令导出JNI的头文件(.h文件)
  4. 使用 Java需要交互的本地代码 实现在 Java中声明的Native方法
    如 Java 需要与 C++ 交互,那么就用C++实现 Java的Native方法
  5. 编译.so库文件
  6. 通过Java命令执行 Java程序,最终实现Java调用本地代码

 

三、NDK与JNI的关系

示意图

 

四、安装NDK和CMake

安装:

  • Android 原生开发套件 (NDK):可以利用这套工具在 Android 应用中使用 C 和 C++ 代码。
  • CMake:一款外部编译工具,可与 Gradle 搭配使用来编译原生库。如果只计划使用 ndk-build,则不需要此组件。
  • LLDB:Android Studio 用于调试原生代码的调试程序。默认情况下,LLDB 将与 Android Studio 一起安装。

具体安装流程:

https://developer.android.google.cn/studio/projects/install-ndk

NDK各个版本之间存在一些不同的问题,并不是越新越好。当偶尔编译C++代码出现一些莫名其妙的Errors并无法排查时,可以考虑更换NDK版本。

 

五、配置CMake

CMake 编译脚本是一个纯文本文件,必须将其命名为 CMakeLists.txt,并在其中包含 CMake 编译 C/C++ 库时需要使用的命令。如果原生源代码文件还没有 CMake 编译脚本,需要自行创建一个,并在其中包含适当的 CMake 命令。

本部分将介绍应该在编译脚本中包含哪些基本命令,以便指示 CMake 在创建原生库时使用哪些源代码文件。要了解详情,请参阅介绍 CMake 命令的官方文档。

在配置新的 CMake 编译脚本后,需要配置 Gradle 以将 CMake 项目作为编译依赖项包含在内,从而让 Gradle 编译原生库,并将其与应用的 APK 打包在一起。

创建 CMake 编译脚本

要创建一个可以用作 CMake 编译脚本的纯文本文件,请按以下步骤操作:

  1. 从 IDE 的左侧打开 Project 窗格,然后从下拉菜单中选择 Project 视图。
  2. 右键点击 your-module 的根目录,然后依次选择 New > File。注意:可以在所需的任何位置创建编译脚本。不过,在配置编译脚本时,原生源代码文件和库的路径将与编译脚本的位置相关。
  3. 输入“CMakeLists.txt”作为文件名,然后点击 OK。

现在,可以通过添加 CMake 命令来配置编译脚本。要指示 CMake 通过原生源代码创建原生库,请将向编译脚本添加 cmake_minimum_required() 和 add_library() 命令:

    # Sets the minimum version of CMake required to build your native library.
    # This ensures that a certain set of CMake features is available to
    # your build.

    cmake_minimum_required(VERSION 3.4.1)

    # Specifies a library name, specifies whether the library is STATIC or
    # SHARED, and provides relative paths to the source code. You can
    # define multiple libraries by adding multiple add_library() commands,
    # and CMake builds them for you. When you build your app, Gradle
    # automatically packages shared libraries with your APK.

    add_library( # Specifies the name of the library.
                 native-lib

                 # Sets the library as a shared library.
                 SHARED

                 # Provides a relative path to your source file(s).
                 src/main/cpp/native-lib.cpp )
    

提示:与指示 CMake 通过源代码文件创建原生库的方式一样,可以使用 add_executable() 命令指示 CMake 改为通过这些源代码文件来创建可执行文件。不过,从原生源代码文件编译可执行文件是可选操作,编译原生库以将其打包到 APK 中即可满足大多数项目要求。

在使用 add_library() 向 CMake 编译脚本添加源代码文件或库时,Android Studio 还会在同步项目后在 Project 视图中显示相关的头文件。不过,为了让 CMake 能够在编译时找到头文件,需要向 CMake 编译文件添加 include_directories() 命令,并指定头文件的路径:

    add_library(...)

    # Specifies a path to native header files.
    include_directories(src/main/cpp/include/)

CMake 使用以下规范来为库文件命名:

liblibrary-name.so

如果在编译脚本中指定“native-lib”作为共享库的名称,CMake 就会创建一个名为 libnative-lib.so 的文件。不过,在 Java 或 Kotlin 代码中加载此库时,请使用 CMake 编译脚本中指定的名称:

    static {
        System.loadLibrary("native-lib");
    }

注意:如果重命名或移除了 CMake 编译脚本中的库,需要在 Gradle 应用相关更改或从 APK 中移除旧版库之前清除项目。要清除项目,请在菜单栏中依次选择 Build > Clean Project。

Android Studio 会自动向 Project 窗格中的 cpp 组添加源代码文件和头文件。通过使用多个 add_library() 命令,可以为 CMake 定义要通过其他源代码文件编译的更多库。

添加 NDK API

Android NDK 提供了一套非常实用的原生 API 和库。通过在项目的 CMakeLists.txt 脚本文件中包含 NDK 库,可以使用其中任何 API。

Android 平台上已存在预编译的 NDK 库,因此无需编译它们或将它们打包到 APK 中。由于 NDK 库已成为 CMake 搜索路径的一部分,因此甚至无需在本地 NDK 安装中指定库的位置,只需为 CMake 提供想要使用的库的名称,并将其与自己的原生库相关联即可。

向 CMake 编译脚本添加 find_library() 命令以找到 NDK 库并将其路径存储为一个变量。可以使用此变量在编译脚本的其他部分引用 NDK 库。以下示例可以找到特定于 Android 的日志支持库,并会将其路径存储在 log-lib 中:

    find_library( # Defines the name of the path variable that stores the
                  # location of the NDK library.
                  log-lib

                  # Specifies the name of the NDK library that
                  # CMake needs to locate.
                  log )

为了让原生库能够调用 log 库中的函数,需要使用 CMake 脚本中的 target_link_libraries() 命令来关联库:

    find_library(...)

    # Links your native library against one or more other native libraries.
    target_link_libraries( # Specifies the target library.
                           native-lib

                           # Links the log library to the target library.
                           ${log-lib} )

NDK 还以源代码的形式包含一些库,在编译和关联原生库时需要使用这些代码。可以使用 CMake 编译脚本中的 add_library() 命令将源代码编译到原生库中。要提供本地 NDK 库的路径,可以使用 Android Studio 自动定义的 ANDROID_NDK 路径变量。

以下命令可以指示 CMake 将 android_native_app_glue.c(用于管理 NativeActivity 生命周期事件和轻触输入)编译到静态库中,并将其关联到 native-lib:

    add_library( app-glue
                 STATIC
                 ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

    # You need to link static libraries against your shared native library.
    target_link_libraries( native-lib app-glue ${log-lib} )

添加其他预编译库

添加预编译库的步骤与为 CMake 指定其他要编译的原生库的步骤相似。不过,由于库已编译,因此需要使用 IMPORTED 标记指示 CMake 将此库导入到项目中。

    add_library( imported-lib
                 SHARED
                 IMPORTED )

然后,需要使用 set_target_properties() 命令指定库的路径,具体步骤如下所示。

某些库会为特定的 CPU 架构提供单独的软件包(或应用二进制接口 (ABI)),并将其组织到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让只使用所需的库版本。要向 CMake 编译脚本添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,可以使用 ANDROID_ABI 路径变量。此变量使用的是 NDK 支持的一组默认 ABI,或者手动配置 Gradle 以使用的一组经过过滤的 ABI。例如:

    add_library(...)
    set_target_properties( # Specifies the target library.
                           imported-lib

                           # Specifies the parameter you want to define.
                           PROPERTIES IMPORTED_LOCATION

                           # Provides the path to the library you want to import.
                           imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

为了让 CMake 能够在编译时定位头文件,需要使用 include_directories() 命令并包含相应头文件的路径:

    include_directories( imported-lib/include/ )

注意:如果想要打包不属于编译时依赖项的预编译库(例如在添加属于 imported-lib 依赖项的预编译库时),无需按以下说明操作来关联库。

要将预编译库关联到自己的原生库,请将其添加到 CMake 编译脚本的 target_link_libraries() 命令中:

    target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

要将预编译库打包到 APK 中,需要使用 sourceSets 块手动配置 Gradle 以包含 .so 文件的路径。编译 APK 后,可以使用 APK 分析器验证 Gradle 会将哪些库打包到 APK 中。

包含其他 CMake 项目

如果想要编译多个 CMake 项目并在 Android 项目中包含它们的输出,可以使用一个 CMakeLists.txt 文件(即关联到 Gradle 的那个文件)作为顶级 CMake 编译脚本,并添加其他 CMake 项目作为此编译脚本的依赖项。以下顶级 CMake 编译脚本使用 add_subdirectory() 命令将另一个 CMakeLists.txt 文件指定为编译依赖项,然后关联其输出,就像处理任何其他预编译库一样。

    # Sets lib_src_DIR to the path of the target CMake project.
    set( lib_src_DIR ../gmath )

    # Sets lib_build_DIR to the path of the desired output directory.
    set( lib_build_DIR ../gmath/outputs )
    file(MAKE_DIRECTORY ${lib_build_DIR})

    # Adds the CMakeLists.txt file located in the specified directory
    # as a build dependency.
    add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                      ${lib_src_DIR}

                      # Specifies the directory for the build outputs.
                      ${lib_build_DIR} )

    # Adds the output of the additional CMake build as a prebuilt static
    # library and names it lib_gmath.
    add_library( lib_gmath STATIC IMPORTED )
    set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                           ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
    include_directories( ${lib_src_DIR}/include )

    # Links the top-level CMake build output against lib_gmath.
    target_link_libraries( native-lib ... lib_gmath )

https://developer.android.google.cn/studio/projects/configure-cmake

 

六、将 Gradle 关联到原生库

要添加原生库项目作为 Gradle 编译依赖项,需要向 Gradle 提供 CMake 或 ndk-build 脚本文件的路径。当编译应用时,Gradle 会运行 CMake 或 ndk-build,并将共享的库打包到 APK 中。Gradle 还会使用编译脚本来了解要将哪些文件添加到 Android Studio 项目中,以便可以从 Project 窗口访问这些文件。如果没有原生源代码文件的编译脚本,则需要先创建 CMake 编译脚本,然后再继续。

Android 项目中的每个模块只能链接到一个 CMake 或 ndk-build 脚本文件。例如,如果想要编译并打包来自多个 CMake 项目的输出,就需要使用一个 CMakeLists.txt 文件作为顶级 CMake 编译脚本(然后将 Gradle 链接到该脚本),并添加其他 CMake 项目作为该编译脚本的依赖项。同样,如果使用的是 ndk-build,则可以在顶级 Android.mk 脚本文件中包含其他 Makefile。

将 Gradle 链接到原生项目后,Android Studio 会更新 Project 窗格,以在 cpp 组中显示源代码文件和原生库,并在 External Build Files 组中显示外部编译脚本。

注意:更改 Gradle 配置后,请务必点击工具栏中的 Sync Project 图标 ,以便让所做的更改生效。此外,如果要在将 CMake 或 ndk-build 脚本文件链接到 Gradle 之后再对文件进行更改,应当从菜单栏中选择 Build > Refresh Linked C++ Projects,将 Android Studio 与更改进行同步。

使用 Android Studio UI

可以使用 Android Studio UI 将 Gradle 链接到外部 CMake 或 ndk-build 项目:

  1. 从 IDE 左侧打开 Project 窗格并选择 Android 视图。
  2. 右键点击想要链接到原生库的模块(例如 app 模块),并从菜单中选择 Link C++ Project with Gradle。会看到一个类似于图 4 所示的对话框。
  3. 从下拉菜单中,选择 CMake 或 ndk-build。   a. 如果选择 CMake,请使用 Project Path 旁的字段为外部 CMake 项目指定 CMakeLists.txt 脚本文件。   b. 如果选择 ndk-build,请使用 Project Path 旁的字段为外部 ndk-build 项目指定 Android.mk 脚本文件。如果 Application.mk 文件与 Android.mk 文件位于相同目录下,Android Studio 也会包含此文件。
  4. 点击 OK。

图 4. 使用 Android Studio 对话框链接外部 C ++ 项目。

手动配置 Gradle

要手动配置 Gradle 以链接到原生库,需要将 externalNativeBuild 块添加到模块级 build.gradle 文件中,并使用 cmake 或 ndkBuild 块对其进行配置:

    android {
      ...
      defaultConfig {...}
      buildTypes {...}

      // Encapsulates your external native build configurations.
      externalNativeBuild {

        // Encapsulates your CMake build configurations.
        cmake {

          // Provides a relative path to your CMake build script.
          path "CMakeLists.txt"
        }
      }
    }

注意:如果要将 Gradle 链接到现有的 ndk-build 项目,请使用 ndkBuild 块(而不是 cmake 块),并提供指向 Android.mk 文件的相对路径。如果 Application.mk 文件与 Android.mk 文件位于相同目录下,Gradle 也会包含此文件。

指定可选配置

可以在模块级 build.gradle 文件的 defaultConfig 块中配置另一个 externalNativeBuild 块,为 CMake 或 ndk-build 指定可选参数和标记。与 defaultConfig 块中的其他属性类似,也可以在编译配置中为每种产品特性重写这些属性。

例如,如果 CMake 或 ndk-build 项目定义多个原生库和可执行文件,则可以使用 targets 属性为指定产品特性编译和打包其中的部分工件。以下示例代码展示了可以配置的部分属性:

    android {
      ...
      defaultConfig {
        ...
        // This block is different from the one you use to link Gradle
        // to your CMake or ndk-build script.
        externalNativeBuild {

          // For ndk-build, instead use the ndkBuild block.
          cmake {

            // Passes optional arguments to CMake.
            arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

            // Sets a flag to enable format macro constants for the C compiler.
            cFlags "-D__STDC_FORMAT_MACROS"

            // Sets optional flags for the C++ compiler.
            cppFlags "-fexceptions", "-frtti"
          }
        }
      }

      buildTypes {...}

      productFlavors {
        ...
        demo {
          ...
          externalNativeBuild {
            cmake {
              ...
              // Specifies which native libraries or executables to build and package
              // for this product flavor. The following tells Gradle to build only the
              // "native-lib-demo" and "my-executible-demo" outputs from the linked
              // CMake project. If you don't configure this property, Gradle builds all
              // executables and shared object libraries that you define in your CMake
              // (or ndk-build) project. However, by default, Gradle packages only the
              // shared libraries in your APK.
              targets "native-lib-demo",
                      // You need to specify this executable and its sources in your CMakeLists.txt
                      // using the add_executable() command. However, building executables from your
                      // native sources is optional, and building native libraries to package into
                      // your APK satisfies most project requirements.
                      "my-executible-demo"
            }
          }
        }

        paid {
          ...
          externalNativeBuild {
            cmake {
              ...
              targets "native-lib-paid",
                      "my-executible-paid"
            }
          }
        }
      }

      // Use this block to link Gradle to your CMake or ndk-build script.
      externalNativeBuild {
        cmake {...}
        // or ndkBuild {...}
      }
    }

要详细了解如何配置产品特性和编译变体,请参阅配置编译变体。如需了解可以使用 arguments 属性为 CMake 配置的变量列表,请参阅使用 CMake 变量。

添加预编译的原生库

如果希望 Gradle 将预编译的原生库打包到 APK 中,请修改默认的源代码文件集配置,以添加预编译 .so 文件所在的目录,如下所示。请注意,若要添加链接到 Gradle 的 CMake 编译脚本的工件,则无需执行此操作。

    android {
        ...
        sourceSets {
            main {
                jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
            }
        }
    }

指定 ABI

默认情况下,Gradle 会针对 NDK 支持的应用二进制接口 (ABI) 将原生库编译到单独的 .so 文件中,并将这些文件全部打包到 APK 中。如果希望 Gradle 仅编译和打包原生库的部分 ABI 配置,可以在模块级文件 build.gradle 中使用 ndk.abiFilters 标记指定这些配置,如下所示:

    android {
      ...
      defaultConfig {
        ...
        externalNativeBuild {
          cmake {...}
          // or ndkBuild {...}
        }

        // Similar to other properties in the defaultConfig block,
        // you can configure the ndk block for each product flavor
        // in your build configuration.
        ndk {
          // Specifies the ABI configurations of your native
          // libraries Gradle should build and package with your APK.
          abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                       'arm64-v8a'
        }
      }
      buildTypes {...}
      externalNativeBuild {...}
    }
    

在大多数情况下,只需要在 ndk 块中指定 abiFilters(如上所示),因为它会指示 Gradle 编译和打包原生库的这些版本。但是,如果想控制 Gradle 应当编译的配置,而不依赖于希望其打包到 APK 中的配置,请在 defaultConfig.externalNativeBuild.cmake 块(或 defaultConfig.externalNativeBuild.ndkBuild 块中)配置另一个 abiFilters 标记。Gradle 会编译这些 ABI 配置,并且只会打包在 defaultConfig.ndk 块中指定的配置。

为了进一步降低 APK 的大小,请考虑基于 ABI 配置多个 APK,而不是使用原生库的所有版本创建一个大型 APK。Gradle 会为想要支持的每个 ABI 单独创建一个 APK,并且仅打包每个 ABI 需要的文件。如果为每个 ABI 配置多个 APK,而不像上面的代码示例中所示的那样指定 abiFilters 标志,则 Gradle 会为原生库编译所有受支持的 ABI 版本,但是仅打包在多 APK 配置中指定的版本。为避免编译不想要的原生库版本,请为 abiFilters 标记和“一个 ABI 多个 APK”配置提供相同的 ABI 列表。

https://developer.android.google.cn/studio/projects/gradle-external-native-builds

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值