Android NDK入门基础笔记

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码。

使用 NDK 将 C 和 C++ 代码编译到原生库中,然后使用 Android Studio 的集成构建系统 Gradle 将原生库打包到 APK 中。Java 代码随后可以通过Java原生接口(JNI)调用原生库中的函数。

NDK的作用:

  • 进一步提升设备性能,以降低延迟,或运行计算密集型应用,如游戏或物理模拟。
  • 重复使用您自己或其他开发者的 C 或 C++ 库。

Android Studio 编译原生库的默认构建工具是CMake。

主要组件:

  • 原生共享库:NDK 从 C/C++ 源代码编译这些库或 .so 文件;
  • 原生静态库:NDK 也可编译静态库或 .a 文件,可将静态库关联到其他库;
  • Java 原生接口 (JNI):JNI 是 Java 和 C++ 组件用以互相通信的接口;
  • 应用二进制接口 (ABI):ABI 可以非常精确地定义应用的机器代码在运行时应该如何与系统交互。NDK 根据这些定义编译 .so 文件。不同的 ABI 对应不同的架构:NDK 为 32 位 ARM、AArch64、x86 及 x86-64 提供 ABI 支持;

开发流程:

  • 设计应用,确定要以 Java 实现的部分,以及要以原生代码形式实现的部分;
  • 像创建任何其他 Android 项目一样创建一个 Android 应用项目;
  • 在“JNI”目录中创建一个描述原生库的 Android.mk 文件,包括名称、标记、关联库和要编译的源文件;
  • 或者,也可以创建一个配置目标 ABI、工具链、发布/调试模式和 STL 的 Application.mk 文件。对于其中任何您未指明的项,将分别使用以下默认值:所有非弃用的ABI;工具链Clang;发布;系统;
  • 将原生源代码放在项目的 jni 目录下;
  • 使用 ndk-build 编译原生(.so.a)库;
  •         编译 Java 组件,生成可执行 .dex 文件;
  •         将所有内容封装到一个 APK 文件中,包括 .so.dex 以及应用运行所需的其他文件。

1.下载NDK和工具:

应用编译和调试原生代码,您需要以下组件:

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

安装配置NDK和CMake

 

2.创建CMake构建脚本:

CMake 构建脚本是一个纯文本文件,必须将其命名为 CMakeLists.txt,并在其中包含 CMake 构建您的 C/C++ 库时需要使用的命令。

CMake命令集

 # 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 )

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

    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 )

     # 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} )

CMake命令解释:

  • cmake_minimum_required():设置CMake所需要的最小版本;
  • add_library():设置共享库的名字,设置共享类型,添加C/C++原生代码;名字规范为liblibrary-name.so,library-name为库名字,如libnative-lib.so共享库为native-lib,在Android中加载如下:
static {
        System.loadLibrary("native-lib");
    }

          共享类型有:STATIC/SHARED/MUDULE,STATIC:是对象文件的存档,用于链接其他目标;SHARED:静态加载和运行时加载;MUDULE:是没有链接到其他目标的插件,但是可以在运行时使用dlopen-link功能动态加载;

          添加C/C++原生代码:添加C/C++的相对路径。

  • find_library():以找到 NDK 库并将其路径存储为一个变量。可以使用此变量在构建脚本的其他部分引用 NDK 库;
  • target_link_libraries():为了让原生库能够调用如 log 库中的函数;

关联到原生库:

  • 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} )

添加其他预构建库:

include_directories( imported-lib/include/ )
    
add_library( imported-lib
                 SHARED
                 IMPORTED )

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 )
  • set_target_properties():命令指定库的路径,${ANDROID_ABI}添加库的多个 ABI 版本的相对路径。
  • IMPORT:标记CMake将预构建库导入项目中;
  • include_directories():定位构建库的头文件;

包含其他CMake项目:

    # 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 )

要构建多个 CMake 项目并在 Android 项目中包含它们的输出,可以使用一个CMakeLists.txt个作为顶级CMake构建脚本,加其他 CMake 项目作为此构建脚本的依赖项;

  • add_subdirectory():将另一个 CMakeLists.txt 文件指定为构建依赖项,然后关联其输出。

 

3.Java原生接口(JNI):

JNI 是指 Java 原生接口。它定义了 Android 从受管理代码(使用 Java 或 Kotlin 编程语言编写)编译的字节码与原生代码(使用 C/C++ 编写)互动的方式。JNI 不依赖于供应商,支持从动态共享库加载代码,虽然较为繁琐,但有时相当有效。

 

尽量减少 JNI 层的占用空间,遵循原则:

  • 尽可能减少跨 JNI 层编组资源的次数;
  • 尽可能避免在使用受管理编程语言编写的代码与使用 C++ 编写的代码之间进行异步通信;
  • 尽可能减少需要接触 JNI 或被 JNI 接触的线程数;
  • 将接口代码保存在少量易于识别的 C++ 和 Java 源位置,以便将来进行重构。

 

JavaVM和JNIEnv:

JNI 定义了两个关键数据结构,即“JavaVM”和“JNIEnv”。两者本质上都是指向函数表的二级指针。(在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。)

  • JavaVM:提供“调用接口”函数,您可以利用此类来函数创建和销毁 JavaVM。理论上,每个进程可以有多个 JavaVM,但 Android 只允许有一个;
  • JNIEnv:提供了大部分 JNI 函数。原生函数都会收到 JNIEnv 作为第一个参数;

 

线程:

所有线程都是 Linux 线程,由内核调度。线程通常从受管理代码启动(使用 Thread.start),但也可以在其他位置创建,然后附加到 JavaVM。Android 不会挂起执行原生代码的线程。如果正在进行垃圾回收,或者调试程序已发出挂起请求,则在线程下次调用 JNI 时,Android 会将其挂起。通过 JNI 附加的线程在退出之前必须调用 DetachCurrentThread

 

jclass/jmethodID/jfieldID:

  • jclass:使用FindClass获取类的对象引用;
  • jmethodID:使用GetFieldID获取字段的ID;
  • jfieldID:使用适当内容获取字段的内容,如GetIntField.

要调用方法,首先要获取类对象引用,然后获取方法 ID。方法 ID 通常只是指向内部运行时数据结构的指针。查找方法 ID 可能需要进行多次字符串比较,但一旦获取此类 ID,便可以非常快速地进行实际调用以获取字段或调用方法。

 

局部引用和全局引用:

传递给原生方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于“局部引用”。这意味着,局部引用在当前线程中的当前原生方法运行期间有效。 在原生方法返回后,即使对象本身继续存在,该引用也无效;

获取非局部引用的唯一方法是通过 NewGlobalRef 和 NewWeakGlobalRef 函数;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值