概念

准备工作

本指南假设您已经熟悉原生编程和Android 开发中的固有概念。

简介

本部分高级别地解释了 NDK 的工作原理。Android NDK 是一套工具,允许您将 C 或 C++(“原生代码”)嵌入到您的 Android 应用中。在 Android 应用中使用原生代码对于希望执行以下一项或多项操作的开发者尤其有用:

  • 在平台之间移植他们的应用。
  • 重用现有库,或提供自己的库供重用。
  • 在某些情况下提高性能,尤其是计算密集型任务(如游戏)。

工作原理

本部分介绍了为 Android 构建原生应用所使用的主要组件,并接着描述了构建和打包过程。

主要组件

在构建您的应用时,您应该了解以下组件:

  • 原生共享库:NDK 从您的 C/C++ 源代码构建这些库(即 .so 文件)。

  • 原生静态库:NDK 还可以构建静态库(即 .a 文件),您可以将其链接到其他库中。

  • Java 原生接口 (JNI):JNI 是 Java 和 C++ 组件相互通信的接口。本指南假设您了解 JNI;有关它的信息,请查阅Java Native Interface Specification

  • 应用二进制接口 (ABI):ABI 准确定义了您的应用机器码在运行时如何与系统交互。NDK 根据这些定义构建 .so 文件。不同的 ABI 对应不同的架构:NDK 支持 32 位 ARM、AArch64、x86 和 x86-64 的 ABI。更多信息请参见Android ABI

  • 清单:如果您编写的应用不包含 Java 组件,则必须在清单中声明 NativeActivity 类。有关如何执行此操作的更多详细信息,请参见使用 native_activity.h 接口

流程

为 Android 开发原生应用的一般流程如下:

  1. 设计您的应用,决定哪些部分用 Java 实现,哪些部分用原生代码实现。

  2. 创建 Android 应用项目,就像创建任何其他 Android 项目一样。

  3. 如果您编写的是纯原生应用,请在 AndroidManifest.xml 中声明 NativeActivity 类。更多信息请参见原生 Activity 和应用

  4. 在“JNI”目录中创建描述原生库的 Android.mk 文件,包括名称、标志、链接库和要编译的源文件。

  5. 您可以选择创建 Application.mk 文件,用于配置目标 ABI、工具链、发布/调试模式和 STL。对于您未指定的任何项,将分别使用以下默认值:

    • ABI:所有未弃用的 ABI
    • 模式:Release
    • STL:system
  6. 将您的原生源代码放在项目的 jni 目录下。

  7. 使用 ndk-build 编译原生 (.so, .a) 库。

  8. 构建 Java 组件,生成可执行的 .dex 文件。

  9. 将所有内容打包到 APK 文件中,该文件包含 .so.dex 和您的应用运行所需的其他文件。

原生 Activity 和应用

Android SDK 提供了一个帮助类 NativeActivity,允许您编写完全原生的 activity。NativeActivity 处理 Android 框架和您的原生代码之间的通信,因此您无需继承它或调用其方法。您只需在 AndroidManifest.xml 文件中声明您的应用为原生应用,然后开始创建您的原生应用。

使用 NativeActivity 的 Android 应用仍在其自己的虚拟机中运行,与其他应用隔离。因此,您仍然可以通过 JNI 访问 Android 框架 API。在某些情况下,例如对于传感器、输入事件和资产,NDK 提供了原生接口,您可以使用这些接口而无需跨 JNI 调用。有关此类支持的更多信息,请参见原生 API

无论您是否开发原生 activity,我们都建议您使用传统的 Android 构建工具创建项目。这样做有助于确保以正确的结构构建和打包 Android 应用。

Android NDK 提供两种选择来实现您的原生 activity:

  • native_activity.h 头文件定义了 NativeActivity 类的原生版本。它包含您创建原生 activity 所需的回调接口和数据结构。由于您的应用主线程处理回调,因此您的回调实现不能阻塞。如果它们阻塞,您可能会收到 ANR(应用无响应)错误,因为您的主线程在回调返回之前无法响应。
  • android_native_app_glue.h 文件定义了一个基于 native_activity.h 接口构建的静态帮助库。它会生成另一个线程,该线程在事件循环中处理回调或输入事件等事务。将这些事件移到单独的线程可以防止任何回调阻塞您的主线程。

<ndk_root>/sources/android/native_app_glue/android_native_app_glue.c 源文件也可用于修改实现。

有关如何使用此静态库的更多信息,请查看 native-activity 示例应用及其文档。有关详细信息,还可参阅 <ndk_root>/sources/android/native_app_glue/android_native_app_glue.h 文件中的注释。

使用 native_activity.h 接口

要使用 native_activity.h 接口实现原生 activity,请执行以下操作:

  1. 在项目的根目录中创建一个 jni/ 目录。此目录存储您的所有原生代码。

  2. AndroidManifest.xml 文件中声明您的原生 activity。

    由于您的应用没有 Java 代码,因此将 android:hasCode 设置为 false

    <application android:label="@string/app_name" android:hasCode="false">
    

    您必须将 activity 标签的 android:name 属性设置为 NativeActivity

    <activity android:name="android.app.NativeActivity"
              android:label="@string/app_name">
    

    meta-data 标签的 android:value 属性指定包含应用入口点(如 C/C++ main)的共享库的名称,省略了库名称中的 lib 前缀和 .so 后缀。

    <manifest>
      <application>
        <activity>
          <meta-data android:name="android.app.lib_name"
                     android:value="native-activity" />
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
    
  3. 为您的原生 activity 创建一个文件,并实现 ANativeActivity_onCreate 变量中命名的函数。当原生 activity 启动时,应用会调用此函数。此函数类似于 C/C++ 中的 main,接收指向 ANativeActivity 结构的指针,该结构包含指向您需要编写的各种回调实现的函数指针。在 ANativeActivity->callbacks 中设置适用的回调函数指针,指向您的回调实现。

  4. ANativeActivity->instance 字段设置为您要使用的任何特定数据实例的地址。

  5. 实现您希望 activity 在启动时执行的任何其他操作。

  6. 实现您在 ANativeActivity->callbacks 中设置的其余回调。有关何时调用回调的更多信息,请参见管理 Activity 生命周期

  7. 开发您的应用的其余部分。

  8. 在项目的 jni/ 目录中创建 Android.mk file,向构建系统描述您的原生模块。更多信息请参见Android.mk

  9. 拥有 Android.mk 文件后,使用 ndk-build 命令编译您的原生代码。

    cd <path>/<to>/<project>
    $NDK/ndk-build
  10. 照常构建和安装您的 Android 项目。如果您的原生代码位于 jni/ 目录中,构建脚本会自动将从该目录构建的 .so 文件打包到 APK 中。

其他示例代码

要下载 NDK 示例,请参见NDK Samples