概念

开始之前

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

简介

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

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

工作原理

本节介绍构建 Android 原生应用中使用的主要组件,并描述构建和打包过程。

主要组件

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

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

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

  • Java 本地接口 (JNI):JNI 是 Java 和 C++ 组件相互通信的接口。本指南假设您了解 JNI;有关 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示例