概念

开始之前

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

简介

本节提供有关 NDK 工作原理的高级说明。Android NDK 是一套工具,允许您将 C 或 C++(“原生代码”)嵌入到您的 Android 应用中。能够在 Android 应用中使用原生代码对于希望执行以下一项或多项操作的开发者特别有用

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

工作原理

本节介绍构建 Android 原生应用时使用的主成分,并继续描述构建和打包过程。

主要组件

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

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

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

  • Java 本地接口 (JNI):JNI 是 Java 和 C++ 组件相互通信的接口。本指南假设您了解 JNI;有关详细信息,请参阅Java 本地接口规范

  • 应用程序二进制接口 (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 类。有关更多信息,请参阅原生活动和应用

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

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

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

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

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

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

原生活动和应用程序

Android SDK 提供了一个辅助类,NativeActivity,它允许您编写一个完全原生的活动。NativeActivity 处理 Android 框架和您的原生代码之间的通信,因此您无需对其进行子类化或调用其方法。您需要做的就是在您的 AndroidManifest.xml 文件中声明您的应用程序为原生应用程序,然后开始创建您的原生应用程序。

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

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

Android NDK 提供了两种选择来实现您的原生活动

  • native_activity.h 头文件定义了 NativeActivity 类的原生版本。它包含创建原生活动所需的回调接口和数据结构。因为应用程序的主线程处理回调,所以您的回调实现不能阻塞。如果它们阻塞,您可能会收到 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 接口实现原生活动

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

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

    因为您的应用程序没有 Java 代码,所以将 android:hasCode 设置为 false

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

    您必须将活动标签的 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. 为您的原生活动创建一个文件,并实现名为 ANativeActivity_onCreate 变量的函数。应用程序在原生活动启动时调用此函数。此函数类似于 C/C++ 中的 main,它接收指向 ANativeActivity 结构的指针,该结构包含指向您需要编写的各种回调实现的函数指针。将 ANativeActivity->callbacks 中适用的回调函数指针设置为回调实现。

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

  5. 实现活动启动时要执行的其他任何操作。

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

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

  8. 在项目的 jni/ 目录中创建一个 Android.mk 文件,以向构建系统描述您的原生模块。有关更多信息,请参阅 Android.mk

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

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

其他示例代码

要下载 NDK 示例,请参阅 NDK 示例