杰瑞科技汇

Android Java如何调用JNI?

  1. 准备环境:配置好 Android Studio 和 NDK。
  2. 编写 Java/Kotlin 代码:声明 native 方法。
  3. 生成 JNI 头文件:使用 javah 或 Android Studio 的工具生成 C/C++ 的函数声明。
  4. 实现 C/C++ 代码:编写具体的 native 方法实现。
  5. 配置 CMake/NDK:告诉编译系统如何编译你的 C/C++ 代码。
  6. 加载库并调用:在 Java 代码中加载库并调用 native 方法。

详细步骤与示例

我们将创建一个简单的示例:Java 传递两个整数给 C/C++,C/C++ 计算它们的和并返回给 Java。

Android Java如何调用JNI?-图1
(图片来源网络,侵删)

步骤 1: 准备环境

  1. 安装 NDK 和 CMake

    • 打开 Android Studio。
    • 进入 Tools -> SDK Manager
    • SDK Platforms 标签页,确保你需要的 Android API 已安装。
    • SDK Tools 标签页,勾选 NDK (Side by side)CMake,然后点击 Apply 安装。
  2. 创建或打开一个项目

    • 创建一个新的 Android 项目,或者打开一个已有的项目。
    • build.gradle (Module: app) 文件中,确保 android 块包含了 NDK 和 CMake 的配置,如果新建项目时勾选了 "Include C++ support",这些配置会自动生成。
    // build.gradle (Module: app)
    android {
        // ... 其他配置
        defaultConfig {
            // ... 其他配置
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
            // 指定要支持的 ABI 架构,这样只会编译对应的库,减小 APK 大小
            ndk {
                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
            }
        }
        externalNativeBuild {
            cmake {
                path "src/main/cpp/CMakeLists.txt"
                version "3.18.1" // 使用你安装的 CMake 版本
            }
        }
    }

步骤 2: 编写 Java/Kotlin 代码

在你的 Java 或 Kotlin 文件中(MainActivity.java),声明一个或多个 native 方法,这些方法只是声明,没有实现。

// app/src/main/java/com/example/myapplication/MainActivity.java
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
    // 声明一个 native 方法
    // 注意:方法名必须遵循特定的规则,我们稍后会讲到
    public native int add(int a, int b);
    static {
        // 加载我们即将创建的 native 库
        // "native-lib" 是我们在 CMakeLists.txt 中定义的库的名称
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        int result = add(10, 20); // 调用 native 方法
        tv.setText("The result from C++ is: " + result);
    }
}

关键点

Android Java如何调用JNI?-图2
(图片来源网络,侵删)
  • public native int add(int a, b);native 关键字告诉 JVM 这个方法的实现在别处(C/C++)。
  • static { System.loadLibrary("native-lib"); }:在类加载时执行,用于加载我们的动态链接库(.so 文件),库名 native-lib 必须与 CMakeLists.txt 中定义的 target_name 完全一致。

步骤 3: 生成 JNI 头文件

这一步的目的是让 C/C++ 编译器知道 add 函数的“长相”(即函数签名)。

在较新的 Android Studio 中,这个过程可以自动化,但了解手动步骤有助于理解原理。

手动方法 (使用 javah)

  1. 先编译你的 Java 代码,生成 class 文件。

    Android Java如何调用JNI?-图3
    (图片来源网络,侵删)
  2. 打开 Android Studio 的 Terminal (View -> Tool Windows -> Terminal)。

  3. 执行 javah 命令,你需要指定包含 native 方法的类的完整包路径和 .class 文件所在的目录。

    # 假设你的 .class 文件在 build/intermediates/javac/debug/classes/ 目录下
    # 你的类是 com.example.myapplication.MainActivity
    javah -d jni -cp build/intermediates/javac/debug/classes com.example.myapplication.MainActivity
  • -d jni:将生成的头文件保存到 jni 目录(如果不存在会自动创建)。
  • -cp ...:指定 .class 文件的类路径。
  • com.example.myapplication.MainActivity:包含 native 方法的完整类名。

执行后,你会在 app/src/main/jni/ 目录下找到一个名为 com_example_myapplication_MainActivity.h 的头文件。

自动方法 (推荐): 现代 Android Studio 通常会自动处理这个过程,当你配置好 CMake 并添加了 native 方法后,当你点击 "Build" -> "Make Project" 或 "Rebuild Project" 时,AS 会自动生成所需的头文件,通常位于 app/build/intermediates/cmake/debug/obj/armeabi-v7a/include/ 目录下,你可以手动复制到 app/src/main/cpp/ 目录下。

步骤 4: 实现 C/C++ 代码

我们来实现 add 函数。

  1. app/src/main/cpp/ 目录下,将上一步生成的头文件(com_example_myapplication_MainActivity.h)复制过来。
  2. 创建一个新的 C++ 源文件,native-lib.cpp

头文件 (com_example_myapplication_MainActivity.h) 的内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_MainActivity */
#ifndef _Included_com_example_myapplication_MainActivity
#define _Included_com_example_myapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myapplication_MainActivity
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_myapplication_MainActivity_add
  (JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif

关键点

  • JNIEXPORTJNICALL:这是 JNI 规定的宏,用于标识函数的导出方式。
  • Java_ + 完整类名(下划线分隔) + 方法名:这就是 Java native 方法在 C/C++ 中的映射规则。
    • com.example.myapplication.MainActivity -> com_example_myapplication_MainActivity
    • add -> add
    • 组合起来就是 Java_com_example_myapplication_MainActivity_add
  • JNIEnv *:指向 JNI 环境的指针,通过它可以调用 JNI 提供的各种函数(例如创建对象、访问数组等)。
  • jobject:指向调用该 native 方法的 Java 对象(这里是 MainActivity 的实例)的指针,如果是 static native 方法,这个参数会是 jobject(指向 Class 对象)。
  • (II)I:这是 JNI 方法签名。
    • 括号内是参数类型。
    • 括号外是返回类型。
    • I 代表 int
    • II 代表两个 int 参数。
    • I 返回一个 int

C++ 实现文件 (native-lib.cpp):

#include "com_example_myapplication_MainActivity.h"
// 实现 add 方法
JNIEXPORT jint JNICALL Java_com_example_myapplication_MainActivity_add(JNIEnv *env, jobject /* this */, jint a, jint b) {
    // 简单的加法运算
    return a + b;
}

注意:第二个参数 jobject this 在这个例子中没有用到,我们将其命名为 /* this */ 并注释掉,以避免编译器警告。

步骤 5: 配置 CMake

配置 CMakeLists.txt 文件,告诉 CMake 如何编译你的 C++ 代码。

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# 定义项目名称
project("myapplication")
# 添加一个库
# SHARED 表示生成一个动态链接库 (.so 文件)
# native-lib 是我们给库起的名字,必须与 System.loadLibrary("native-lib") 中的名字一致
add_library(
        native-lib
        SHARED
        native-lib.cpp)
# 查找 Android 的日志库
find_library(
        log-lib
        log)
# 将日志库链接到我们的 native-lib
# 这样我们就可以在 C/C++ 代码中使用 __android_log_print 来打印日志
target_link_libraries(
        native-lib
        ${log-lib})

步骤 6: 运行和调试

你已经完成了所有配置,点击 Android Studio 的 "Run" 按钮,将应用安装到模拟器或真机上。

MainActivity 启动时,它会执行 onCreate 方法,调用 add(10, 20),这个调用会通过 JNI 机制,执行 native-lib.cpp 中的 Java_com_example_myapplication_MainActivity_add 函数,计算 10 + 20 = 30,然后将结果 30 返回给 Java 代码,屏幕上会显示 "The result from C++ is: 30"。


JNI 数据类型映射

Java 和 C/C++ 的数据类型不是一一对应的,下表是常用的映射关系:

Java 类型 JNI 类型 C/C++ 类型 描述
byte jbyte signed char 8位有符号整数
short jshort short 16位有符号整数
int jint int 32位有符号整数
long jlong long long 64位有符号整数
float jfloat float 32位浮点数
double jdouble double 64位浮点数
char jchar unsigned short 16位 Unicode 字符
boolean jboolean unsigned char 8位布尔值 (0=false, 1=true)
Object jobject void * 任何 Java 对象
Class jclass void * Java Class 对象
String jstring void * Java String 对象
int[] jintArray void * Java int 数组
... ... ... ...

调用 JNI 的核心流程可以概括为:

  1. Java 声明:用 native 关键字定义方法。
  2. 加载库:用 System.loadLibrary() 加载编译好的 .so 文件。
  3. 生成签名:根据 Java 方法的包名、方法名和参数列表,生成 C/C++ 函数名 Java_...
  4. C/C++ 实现:按照 JNI 规则实现函数,接收 JNIEnv*jobject 等参数,并进行类型映射。
  5. 编译配置:通过 CMake 将 C/C++ 源文件编译成动态库。

掌握这个流程后,你就可以将计算密集型、需要与硬件交互或复用现有 C/C++ 库的逻辑放到 native 层,从而提升 Android 应用的性能和功能。

分享:
扫描分享到社交APP
上一篇
下一篇