65.9K
CodeProject 正在变化。 阅读更多。
Home

Android NDK 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.49/5 (15投票s)

2014 年 9 月 30 日

CPOL

5分钟阅读

viewsIcon

34151

downloadIcon

450

使用 Hello World 应用程序的基本 NDK 实现。

目录

前言

本教程不包含在你的机器上安装/配置 NDK。相反,它将通过简单的“Hello World”示例解释如何设置 Java 和 NDK 之间的基本集成。

为什么选择 NDK?

NDK (Native Development Kit) 允许你使用 C/C++ 语言编写代码,然后通过 JNI (Java Native Interface) 从你的 Java 应用程序中调用它。

在 Java 中使用原生代码是合理的,尤其是在处理位/字节操作时,例如位图的压缩/解压缩。

NDK 不会像 Java 那样通过 JVM 进行解释,而是使用操作系统 API(在 Android 的情况下是 Linux)进行这些操作,因此其性能会快得多。

NDK 的一个主要优点是你可以使用 `malloc()` 方法调用自定义内存分配。但是,在原生代码中,没有 GC(垃圾回收),因此你需要自己释放内存。

潜在地,你可以提高应用程序的性能,但有时也可能过度使用,所以要适当地使用它。

Java-NDK 集成通用步骤

  1. 在 Java 类中声明原生方法。
  2. 根据原生实现创建头文件。
  3. 在 C/C++ 类中实现头文件(原生端)。
  4. 创建 `Android.mk` 文件。
  5. 将原生代码编译成 `.so` 包/包。
  6. 调用 Java 原生声明方法。

步骤 1:在 Java 类中声明原生方法

我们需要做的第一件事是在我们的 Android 应用程序类中创建原生方法声明。

public class NDK_Methods
{
     public static native String SayHello();
}

步骤 2:创建头文件

Javah 是什么?

Javah 是一个 Java 工具,它从 Java 类生成 C 头文件,以提供 Java 和 C 代码可以交互的接口。

使用 javah 工具创建头文件

在我们创建了原生的 `SayHello()` 方法声明后,我们需要创建一个头文件,该文件将在原生端的 C 类中实现。

步骤

  1. 转到项目的根目录。
  2. 打开 CMD。
  3. 输入:
    javah -classpath bin/classes/ -d jni/ndk.NDK_Methods
  • `-classpath bin/classes/`:类目录的位置,其中包含你的 Android 应用程序的已编译 Java 类。
  • `-d jni/ndk.NDK_Methods`:从中生成头类的完全限定类名。

生成的头文件将在项目下的 `jni` 目录中创建。

在我们的示例中,我们将得到以下头文件:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class ndk_NDK_Methods */
#ifndef _Included_ndk_NDK_Methods
#define _Included_ndk_NDK_Methods
#ifdef __cplusplus
extern "C" {

#endif

/*
 * Class:     ndk_NDK_Methods
 * Method:    SayHelo
 * Signature: ()Ljava/lang/String;
 */

JNIEXPORT jstring JNICALL Java_ndk_NDK_1Methods_SayHelo(JNIEnv *, jclass);

#ifdef __cplusplus

}

#endif

#endif

我们的 Java 方法声明

public static native String SayHello();

已生成为

JNIEXPORT jstring JNICALL Java_ndk_NDK_1Methods_SayHelo (JNIEnv *, jclass);

请注意,生成的名称是 Java,后跟包名、类名和方法名,用下划线分隔。

由于我们没有向函数传递任何参数,我们只接受默认参数:

  1. `JNIEnv` – 这是我们从原生实现与 Java 通信的指针。
  2. `jclass` – 调用该函数 的类。

步骤 3:实现头文件

现在我们有了一个头文件,我们需要为我们的方法定义一个实现。

我们需要在 `jni` 目录中创建一个带有 c 或 cpp 扩展名的新文件,并将 `SayHello()` 方法声明复制到其中。

//specifies the header file that is included
#include <ndk_NDK_Methods.h>

JNIEXPORT jstring JNICALL Java_ndk_NDK_1Methods_SayHello(JNIEnv *env, jobject thiz )
{
         return (*env)->NewStringUTF(env, "Hello from JNI!");
}

请注意,我们在方法中返回的是 `jstring`,这与 C 中的 `string` 不同。我们需要返回一个 Java 对象,因此我们通过 `JniEnv` 指针调用 `NewStringUTF()` 方法来实现。

步骤 4:创建 Android.mk 文件

为了将我们的文件编译成 `.so` 包,我们需要在 `jni` 目录中定义一个 `Android.mk` 文件。

`Android.mk` 文件向构建系统描述你的源代码。

`Android.mk` 包含以下字段:

  • `LOCAL_PATH := $(call my-dir)` - `Android.mk` 必须以这个定义开始,它定义了源代码的位置。“my-dir”宏是 `Android.mk` 文件的目录。
  • **`include $(CLEAR_VARS)`** – 清除可能从先前模块构建中设置的所有变量。
  • **`LOCAL_MODULE := native_lib`** – 设置用作模块标识符的名称,该名称稍后在 Java 中使用。
  • `LOCAL_SRC_FILES := native_lib`** – 将要编译到你的模块中的文件,无需指定头文件,系统会处理。
  • `include $(BUILD_SHARED_LIBRARY)` – 确保共享库成为此 make 的一部分。

Application.mk

编译不是必需的,但在我们的情况下,我们指定了:

  • **`APP_ABI := all`** – 为所有支持的 CPU 设置编译。

如果我们不指定此项,编译将仅针对默认 CPU 进行。你也可以明确指定你的 CPU 目标编译。

步骤 5:编译原生代码

ndk-build 是什么?

ndk-build 是一个 NDK 工具,负责将你的原生代码编译成可执行文件。

编译

创建 `Android.mk` 文件后,我们可以使用 ndk-build 工具创建 `.so` 包。

步骤

  1. 转到你的 Android 应用程序的根目录。
  2. 使用 ndk-build 工具的完整路径启动它,在我的情况下:*`C:/android_ndk/ndk-build`*。

*`C:/android_ndk`* – 你下载 NDK 的目录。

**ndk-build** – 该工具。

这将会在我们项目下的 `libs` 目录中创建一个 `.so` 包/包。

步骤 6:调用原生方法

我们剩下的要做的就是在我们的应用程序中从 Java 调用 JNI 方法。

String ndkMessage = NDK_Methods.SayHelo();

这将调用 `NDK_Methods` 类中的 `static` 方法,该方法将调用在头文件中声明并在我们的 C 类中实现的 `Java_ndk_NDK_1Methods_SayHelo()` 方法。 

© . All rights reserved.