Android之JNI详解

Android之JNI详解

简介

JNI(Java Native Interface) 是 Java 提供的一种编程框架,用于在 Java 应用程序中调用和与用其他编程语言(如 C、C++ 等)编写的本地代码(Native Code)进行交互。它允许 Java 程序访问操作系统或硬件提供的底层功能。

创建项目

  1. 在AS中选择Native C++创建项目
  2. 项目结构
  • native.cpp:实现本地代码(Native Code)的C++源文件。
  • CmakeList.txt :是 CMake 工具使用的配置文件,用于指定如何构建一个 C/C++ 项目。CMake 是一个跨平台的构建系统生成器,它可以根据 CMakeLists.txt 文件生成适合特定平台和编译器的构建文件。

注册

动态注册

  1. 简介:与静态注册不同,动态注册通过调用 JNI 提供的RegisterNatives函数,在程序运行时显式地注册本地方法。
  2. 实现步骤
    重要点:构建JNINativeMethod结构体:
java 复制代码
typedef struct {
    const char* name; // java类对应的方法名
    const char* signature;	//函数签名
    void*       fnPtr;	//cpp方法的指针函数
} JNINativeMethod;
  • 步骤一:
java 复制代码
package com.hzh.jnidemo;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    
    static {
        System.loadLibrary("jnidemo");
    }
    
    public native String stringFromJNI();

    public native String jniTest();
}
  • 步骤二:
java 复制代码
//cpp交互方法,这里是属于native的命名
jstring native_stringFromJNI(JNIEnv *env,jobject thiz){
    return env->NewStringUTF("动态注册成功!");
}
jstring native_jniTest(JNIEnv *env, jobject thisz) {
    std::string str = "动态注册成功!";
    return env->NewStringUTF(str.c_str());
}

// 需要动态注册native方法的类名
static const char *mClassName = "com/hzh/jnidemo/MainActivity";

// 动态注册函数结构数组
static const JNINativeMethod methods[] = {
        {"jniTest",	//对应java交互类的方法名
         "()Ljava/lang/String;", //对应方法名的函数签名
         (jstring *) native_jniTest //对应cpp交互的指针函数
        },
        {"stringFromJNI",
         "()Ljava/lang/String;",
         (void *) native_stringFromJNI}
};

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv* env = NULL;
    // 1. 获取 JNIEnv,这个地方要注意第一个参数是个二级指针
    int result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
    // 2. 是否获取成功
    if(result != JNI_OK){
        return JNI_VERSION_1_6;
    }
    // 3. 注册方法,通过FindClass寻找
    jclass classMainActivity = env->FindClass(mClassName);
    //RegisterNatives 函数接受三个参数:
    //jclass clazz:Java 类的引用。
    //const JNINativeMethod* methods:指向 JNINativeMethod 结构体数组的指针,描述 Java 方法与本地方法的映射关系。
    //jint nMethods:映射关系的数量。
    result = env->RegisterNatives(classMainActivity,methods, 2);
    if(result != JNI_OK){
        return JNI_VERSION_1_2;
    }
    return JNI_VERSION_1_6;
}
  

JNI_OnLoad是在初始化的时候调用,参考activity的生命周期,通过FindClass找到对应的class,最后通过RegisterNatives来实现动态注册。需要留意的是JNINativeMethod结构体以及RegisterNatives的参数。

静态注册

  1. 简介:通过 JNI 规范中约定的命名规则,将 Java 方法与本地(Native)方法的实现直接映射。这种映射关系在编译时由编译器根据方法名和签名自动生成。
  2. Native命名规则 :遵循Java_包名_类名_方法名的格式,包咯面的"."需要转换成"_",例如Java 类 com.hzh.jnidemo中的方法 MainActivity,对应的 C/C++ 函数名应为com_hzh_jnidemo_MainActivity。
  3. 原理:Java 编译器会根据 native 方法的声明生成方法签名,而 C/C++ 编译器会根据命名规则生成符号表。JVM 在加载类时,通过方法签名匹配符号表中的函数名,完成绑定。
  4. 实现步骤
  • Java端的实现如下:
    通过System.loadLibrary("*****")进行加载交互。
java 复制代码
package com.hzh.jnidemo;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("jnidemo");
    }
	
	...
	
    public native String stringFromJNI();
}
  • Native端的实现:
java 复制代码
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
//*遵循命名规则实现方法*
Java_com_hzh_jnidemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

关键词解读

  • extern "C":在 c++ 中可以使用使用 c 代码
  • JNIEXPORT:是一个宏,用于导出本地方法。
  • JNICALL:是一个宏,用于指定调用约定(Calling Convention),通常是个空的宏定义。
  • JNIEnv:是一个指向JNI函数的指针结构体。
    作用:调用JNI函数,如创建Java对象、访问Java字段、调用Java方法等。
    示例:
java 复制代码
JNIEnv *env;
jclass clazz = (*env)->FindClass(env, "class");
  • JavaVM:一个指向Java虚拟机的指针,表示整个JVM实例。
  • jclass:表示Java类的引用。
  • jobject:表示Java对象的引用。
  • jmethodID:表示Java方法的标识符。
  • jfieldID:表示Java字段的标识符。

基础数据类型

引用类型

类型签名

  • 简介:是用于描述 Java 方法或字段的特定字符串。JNI 签名是方法描述符的一部分,它包含了方法的参数类型和返回值类型,确保本地代码能够正确调用 Java 方法或访问 Java 字段。每次GetMethodID 或 GetFieldID或者返回数据都会用到。
  • 签名格式:
    方法签名的格式为:(参数类型签名)返回值类型签名
    示例:
java 复制代码
int add(int a, int b) → (II)I
String getName() → ()Ljava/lang/String;
void print(String message) → (Ljava/lang/String;)V

引用java对象

Field/Method ID:JNIEvn 操作 java 对象时会使用 java 中的反射,引用某个属性都需要 field 和 method 的 id,而id都是指针类型。
实现步骤:

  1. 获取 JNIEnv 指针:
  2. 查找类:
  • 使用 FindClass 函数获取 Java 类的引用。
  • 获取方法 ID 或字段 ID:
  • 使用 GetMethodID 或 GetFieldID 获取方法或字段的标识符。
  1. 操作 Java 对象:
  • 使用 NewObject、CallObjectMethod、SetObjectField 等函数创建或操作 Java 对象。

示例:获取一个class的getName()方法

java 复制代码
extern "C" JNIEXPORT jstring JNICALL
Java_com_hzh_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
    // 1. 获取 thiz 的 class,也就是 java 中的 Class 信息
    jclass thisclazz = env->GetObjectClass(thiz);
    // 2. 根据 Class 获取 getClass 方法的 methodID
    //GetMethodID/GetFieldI的参数:
	//jclass
    //filed/methon name 
    //类型签名: JNI 使用 jvm 的类型签名
    jmethodID mid_getClass = env->GetMethodID(thisclazz, "getClass", "()Ljava/lang/Class;");
    // 3. 执行 getClass 方法,获得 Class 对象
    jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);
    // 4. 获取 Class 实例
    jclass clazz = env->GetObjectClass(clazz_instance);
    // 5. 根据 class  的 methodID
    jmethodID mid_getName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
    // 6. 调用 getName 方法
    jstring name = static_cast<jstring>(env->CallObjectMethod(clazz_instance, mid_getName));
    
    ......
    
}

JNI引用与释放

  • 局部引用(Local Reference):参考java 中的局部变量
java 复制代码
    // 查找类
    jclass myClass = (*env)->FindClass(env, "com/hzh/jnidemo/MainActivity");
    // 获取方法 ID
    jmethodID myMethod = (*env)->GetMethodID(env, myClass, "stringFromJNI", "()Ljava/lang/String");
    // 调用 Java 方法
    (*env)->CallVoidMethod(env, obj, myMethod);
  • 全局引用(Global Reference):参考java 中的全局变量
java 复制代码
 	// 创建局部引用
    jobject localRef = (*env)->NewObject(env, /* 类和方法 ID 略 */);
    // 创建全局引用
    globalRef = (*env)->NewGlobalRef(env, localRef);

	//释放
	//局部的释放
	DeleteLocalRef(localRef)
	//全局的释放
	if (globalRef != NULL) {
        (*env)->DeleteGlobalRef(env, globalRef);
        globalRef = NULL;
    }
  • 弱全局引用(Weak Global Reference):参考java 中的弱引用
java 复制代码
	// 创建引用
 	jobject localRef =(*env)->NewObject(env, /* 类和方法 ID 略 */);;
    weakRef = (*env)->NewWeakGlobalRef(env, localRef);

	//释放
	DeleteLocalRef(localRef)

cmake配置文件

java 复制代码
# 设置最低版本
cmake_minimum_required(VERSION 3.22.1)

# 引用的项目名
project("jnidemo")

# 创建的库名
add_library(
        # 库名字
        ${CMAKE_PROJECT_NAME}
        # 设置是动态库(SHARED)还是静态库(STATIC)
        SHARED
        # 设置库文件
        native-lib.cpp)

# 加载的库,比如第三方的库,脚本等
target_link_libraries(${CMAKE_PROJECT_NAME}
        android
        log)

导入android项目的配置:

java 复制代码
   externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
相关推荐
xiangxiongfly9158 分钟前
Android 沉浸式状态栏
android
wuxu_549 分钟前
Android:坑-dialog失焦主动关闭崩溃
android
那天的烟花雨12 分钟前
android display 笔记(十一)surfaceflinger 如何将图层传到lcd驱动的呢?
android·笔记
wuxu_5412 分钟前
Android:坑-协程-订阅时序导致收不到流
android
你说你说你来说13 分钟前
安卓开发Intent详细介绍和使用
android·笔记
刘龙超1 小时前
如何应对 Android 面试官 -> 电量如何优化?
android·java
CYRUS_STUDIO1 小时前
Android NDK 编译 so 文件 抹除导出符号 反逆向
android·安全·逆向
tangweiguo030519872 小时前
Kotlin 集合过滤全指南:all、any、filter 及高级用法
android·kotlin
_一条咸鱼_2 小时前
大厂Android面试秘籍:Activity 配置变更处理(十)
android·面试·kotlin
怒放的生命.2 小时前
《MySQL从入门到精通》
android·数据库·mysql