概念

开始之前

本指南假设您已熟悉原生编程及 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 支持。如需了解详细信息,请参阅 ABI 管理
  • 清单:如果您编写的应用不包含 Java 组件,您必须在清单中声明 NativeActivity 类。原生 Activity 和应用的“使用 native_activity.h 接口”部分进一步详细介绍如何执行此操作。

流程

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

  1. 设计应用,确定要在 Java 中实现的部分,以及要以原生代码形式实现的部分。

    :虽然可以完全避免 Java,但您可能发现,Android Java 框架对于包括控制显示和 UI 在内的任务很有用。

  2. 像创建任何其他 Android 项目一样创建一个 Android 应用项目。
  3. 如果要编写纯原生应用,请在 AndroidManifest.xml 中声明 NativeActivity 类。如需了解详细信息,请参阅原生 Activity 和应用
  4. 在“JNI”目录中创建一个描述原生库的 Android.mk 文件,包括名称、标志、链接库和要编译的源文件。
  5. 或者,也可以创建一个配置目标 ABI、工具链、发行/调试模式和 STL 的 Application.mk 文件。对于其中任何您未指明的项目,将分别使用以下默认值:
    • ABI:所有非弃用的 ABI
    • 工具链:Clang
    • 模式:发行
    • STL:系统
  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 调用。如需了解有关此类支持的详细信息,请参阅 Android NDK 原生 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 来源也可供使用,允许您修改实现。

如需了解有关如何使用此静态库的详细信息,请检查原生 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。
  3. 因为您的应用没有 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">
    

    :您可以为 NativeActivity 创建子类。如果创建子类,请使用子类的名称,而不是 NativeActivity

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

              <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>
    
  4. 创建用于原生 Activity 的文件,并实现 ANativeActivity_onCreate 变量中指定的函数。应用在原生 Activity 启动时会调用此函数。此函数类似于 C/C++ 中的 main,用于接收 ANativeActivity 结构的指针,其中包含您需要写入的各个回调实现的函数指针。在 ANativeActivity->callbacks 中设置回调实现的适用回调函数指针。
  5. ANativeActivity->instance 字段设置为要使用的特定数据的任何实例的地址。
  6. 实现您希望 Activity 在启动时执行的任何其他操作。
  7. 实现您在 ANativeActivity->callbacks 中设置的其余回调。如需了解何时调用回调的详细信息,请参阅管理 Activity 生命周期
  8. 开发应用的其余部分。
  9. 在项目的 jni/ 目录中创建 Android.mk file,向构建系统描述您的原生模块。如需了解详细信息,请参阅 Android.mk
  10. 在创建 Android.mk 文件后,使用 ndk-build 命令编译原生代码。
  11. $ cd <path>/<to>/<project>
    $ <ndk>/ndk-build
    
  12. 像平常一样构建和安装 Android 项目。如果原生代码在 jni/ 目录中,构建脚本会自动将从其构建的 .so 文件封装到 APK 中。

更多示例代码

如需下载 NDK 示例,请参阅 NDK 示例