【Android】深入浅出 JNI

文章目录

    • [一、JNI 概述](#一、JNI 概述)
      • [1.1 什么是 JNI](#1.1 什么是 JNI)
      • [1.2 为什么需要 JNI](#1.2 为什么需要 JNI)
      • [1.3 JNI 的实现](#1.3 JNI 的实现)
    • [二、JNI 开发基础](#二、JNI 开发基础)
      • [2.1 开发环境配置](#2.1 开发环境配置)
      • [2.2 JNI 基本工作流程](#2.2 JNI 基本工作流程)
        • [2.2.1 Java 调用 Native 代码](#2.2.1 Java 调用 Native 代码)
        • [2.2.2 Native 代码调用 Java](#2.2.2 Native 代码调用 Java)
    • 参考资料

一、JNI 概述

1.1 什么是 JNI

JNI 是一套标准规范,允许运行在 Java 虚拟机中的 Java/Kotlin 代码 与能够与本地代码 (Native Code,通常是 C 或 C++) 进行安全、高效的双向通信。

JNI 规范定义的主要内容有:

  • 数据类型映射规范:定义了Java类型与本地C/C++类型之间的完整映射关系,包括基本类型(如jint对应Java的int)、引用类型(如jobject对应Java对象)以及数组类型(如jintArray)。
  • 方法命名和注册规范 规定了本地方法的命名规则,采用"Java_*包名_*类名_方法名"的格式。同时定义了静态注册和动态注册两种方式,以及 JNIEXPORT 和 JNICALL 等宏定义的使用规范。
  • 函数接口表规范: JNI 通过JNIEnv指针提供了一套完整的函数接口表,包含了访问Java对象、调用Java方法、处理异常、管理内存引用等所有必要操作。每个线程都有独立的 JNIEnv 实例。
  • 引用管理规范: 定义了局部引用、全局引用和弱全局引用三种引用类型的管理机制,以及相应的创建、删除和生命周期管理规则,确保内存安全。
  • 异常处理规范: 规范了如何在本地代码中抛出和捕获Java异常,以及异常检查和处理的标准流程。
  • 线程管理规范: 定义了本地线程如何附加到Java虚拟机,以及线程本地存储的管理方式。
  • 字符串和数组操作规范: 提供了UTF-8和UTF-16字符串的转换机制,以及数组元素的访问和修改规范。

1.2 为什么需要 JNI

  • 性能关键:对于图形渲染、物理模拟、音视频编解码等计算密集型任务,C/C++ 代码通常能提供比 Java/Kotlin 更高的执行效率。
  • 重用现有库:有大量成熟、高性能的 C/C++ 开源库(如 OpenCV, FFmpeg, SQLite)。通过 JNI,可以直接在 Android 应用中使用这些库,避免重复造轮子。
  • 安全性:某些底层系统调用或硬件特性,可能无法通过标准的 Android SDK 直接访问,需要借助 Native 代码。
  • 硬件操控: 直接操作特定硬件(如传感器底层、GPU 加速)往往需要 C/C++ 的底层访问能力

1.3 JNI 的实现

Android 对 JNI 的支持主要体现在:

  • 系统运行时实现 :JNI 的实现位于 Android 源码的 frameworks/base/core/jni/ 目录下,这些代码会被编译成 libandroid_runtime.so 动态库,放置在目标系统的 /system/lib 目录中。
  • 开发工具支持 :为方便开发者编写符合 JNI 规范的代码,Android 官方提供了 NDK(Native Development Kit) 工具集。NDK可以将符合 JNI 规范的代码最终生成可在 Android 系统上运行的本地库。其中,$NDK_HOME/toolchains/llvm/prebuilt/<host-arch>/sysroot/usr/include/jni.h 为 JNI 开发阶段的接口声明,定义了所有 JNI 类型和函数。

二、JNI 开发基础

2.1 开发环境配置

  • 开发工具: NDK
  • 构建系统:CMake(推荐) 或者 ndk-build

2.2 JNI 基本工作流程

JNI 的核心是实现 Java 与 C/C++ 代码的双向通信,其工作流程主要包含两个方向。

2.2.1 Java 调用 Native 代码

具体步骤如下:

  1. 声明 native 方法 :在 Java 类中使用 native关键字声明方法。
java 复制代码
public native String stringFromJNI();
  1. 加载本地库 :使用 System.loadLibrary()加载包含实现的动态库。
java 复制代码
static {
    System.loadLibrary("nativeapptest");
}
  1. 绑定注册函数 :在 C/C++ 中,按照 JNI 规范实现函数。假设使用静态函数注册方式,当 Java 调用 stringFromJNI()时,虚拟机会根据命名规则自动找到并执行对应的 C/C++ 函数。本地函数示例如下:
cpp 复制代码
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativeapptest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject thiz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

其中,

  • extern "C" 是C++中的一个链接指示符,用于解决C++和C语言之间的名称修饰(Name Mangling)问题。

    • C++编译器:为了支持函数重载、类、命名空间等特性,会对函数名进行"修饰",添加额外的信息(如参数类型、命名空间等)
    • C编译器:不会对函数名进行修饰,保持原始函数名
  • JNIEXPORT:指定函数的可见性和导出属性,确保函数能够被Java虚拟机找到和调用

  • JNICALL:指定函数的调用约定(Calling Convention),确保参数传递和栈清理的一致性

  • Java_com_example_nativeapptest_MainActivity_stringFromJNI - 函数名(遵循JNI命名规范)

  • JNIEnv *env : JNI(Java Native Interface)环境指针,是 native 代码与 Java 代码交互的核心接口

  • jobject thiz - JNI环境参数和调用对象,用于调用Java方法、访问Java对象、操作Java数据等

这两个宏确保了native函数在不同平台上的正确导出和调用。

2.2.2 Native 代码调用 Java

在某些情况下(如异步回调),也需要从 Native 层主动调用 Java 层的方法。此过程不需要"注册",但需遵循明确的查找和调用步骤,

Native 代码调用 Java 的核心步骤如下:

  1. 获取 JNIEnv 指针:本地代码通过 JNIEnv 接口指针访问 Java 功能。
  2. **查找目标类:**使用 FindClass通过类名获取类的引用 (jclass)。
  3. **获取方法ID:**使用 GetMethodIDGetStaticMethodID,通过方法名和类型签名获取方法的引用 (jmethodID)。
  4. 调用 Java 方法 :通过 Call<Type>Method系列函数进行实际调用。

示例如下:

(1) Java 类:提供回调方法

java 复制代码
public class JavaClassToCall {
    // 实例方法:字符串处理
    public String processString(String input) {
        return "Processed: " + input.toUpperCase();
    }
    
    // 静态方法:数学计算
    public static int addNumbers(int a, int b) {
        return a + b;
    }
    
    // 无返回值方法
    public void logMessage(String message) {
        System.out.println("[JAVA] " + message);
    }
}

(2) C/C++ :查找并调用 Java 方法

cpp 复制代码
#include <jni.h>
#include <string>

// 本地代码调用Java方法的示例
void callJavaMethods(JNIEnv* env, jobject callingObject) {
    // 1. 获取JNIEnv指针(通常由调用方传入)

    // 2. 查找Java类
    jclass javaClass = env->FindClass("com/example/JavaClassToCall");
    if (javaClass == nullptr) {
        // 处理类找不到的情况
        return;
    }

    // === 调用实例方法 ===
    // 2.1 获取实例方法ID
    jmethodID processStringMethod = env->GetMethodID(
        javaClass, 
        "processString", 
        "(Ljava/lang/String;)Ljava/lang/String;");

    if (processStringMethod != nullptr) {
        // 3.1 调用实例方法
        jstring inputStr = env->NewStringUTF("hello from native");
        jstring resultStr = (jstring)env->CallObjectMethod(
        callingObject, 
        processStringMethod, 
        inputStr);

        // 处理返回的字符串
        const char* resultCStr = env->GetStringUTFChars(resultStr, nullptr);
        printf("Java returned: %s\n", resultCStr);
        env->ReleaseStringUTFChars(resultStr, resultCStr);
        env->DeleteLocalRef(inputStr);
        env->DeleteLocalRef(resultStr);
    }

    // === 调用静态方法 ===
    // 2.2 获取静态方法ID
    jmethodID addNumbersMethod = env->GetStaticMethodID(
        javaClass, 
        "addNumbers", 
        "(II)I");

    if (addNumbersMethod != nullptr) {
        // 3.2 调用静态方法
        jint sum = env->CallStaticIntMethod(
        javaClass, 
        addNumbersMethod, 
        10, 20);

        printf("Java static method returned: %d\n", sum);
    }

    // === 调用无返回值方法 ===
    // 2.3 获取void方法ID
    jmethodID logMessageMethod = env->GetMethodID(
        javaClass, 
        "logMessage", 
        "(Ljava/lang/String;)V");

    if (logMessageMethod != nullptr) {
        // 3.3 调用void方法
        jstring message = env->NewStringUTF("Message from native code");
        env->CallVoidMethod(
            callingObject, 
            logMessageMethod, 
            message);
        env->DeleteLocalRef(message);
    }

    // 4. 释放本地引用
    env->DeleteLocalRef(javaClass);
}

参考资料

Java Native Interface Specification: Contents

相关推荐
L-岁月染过的梦2 小时前
前端使用JS实现端口探活
开发语言·前端·javascript
廋到被风吹走2 小时前
【Java】【Jdk】Jdk11->Jdk17
java·开发语言·jvm
nike0good2 小时前
Goodbye 2025 题解
开发语言·c++·算法
Sheep Shaun2 小时前
STL中的unordered_map和unordered_set:哈希表的快速通道
开发语言·数据结构·c++·散列表
jllllyuz2 小时前
基于帧差法与ViBe算法的MATLAB前景提取
开发语言·算法·matlab
DsirNg2 小时前
CategoryTree 性能优化完整演进史
开发语言·前端
2501_944446002 小时前
Flutter&OpenHarmony字体与排版设计
android·javascript·flutter
消失的旧时光-19432 小时前
mixin 写一个 Flutter 的“埋点 + 日志 + 性能监控”完整框架示例
android·flutter
兜兜转转了多少年2 小时前
《Python 应用机器学习:代码实战指南》笔记2 从0理解机器学习 —— 核心概念全解析
笔记·python·机器学习