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'
        }
    }
相关推荐
练习本41 分钟前
Android系统架构模式分析
android·java·架构·系统架构
每次的天空6 小时前
Kotlin 内联函数深度解析:从源码到实践优化
android·开发语言·kotlin
练习本6 小时前
Android MVC架构的现代化改造:构建清晰单向数据流
android·架构·mvc
早上好啊! 树哥6 小时前
android studio开发:设置屏幕朝向为竖屏,强制应用的包体始终以竖屏(纵向)展示
android·ide·android studio
YY_pdd7 小时前
使用go开发安卓程序
android·golang
Android 小码峰啊9 小时前
Android Compose 框架物理动画之捕捉动画深入剖析(29)
android·spring
bubiyoushang8889 小时前
深入探索Laravel框架中的Blade模板引擎
android·android studio·laravel
cyy2989 小时前
android 记录应用内存
android·linux·运维
CYRUS STUDIO9 小时前
adb 实用命令汇总
android·adb·命令模式·工具
这儿有一堆花10 小时前
安卓应用卡顿、性能低下的背后原因
android·安卓