当前位置: 首页 > news >正文

Android JNI开发

1、Android JNI 动态库加载方式

1.1、静态加载

静态加载指的是在java类加载时自动加载本地库,在同一个进程中对同一个库名只会加载一次。有以下特点:

  • 使用System.loadLibrary(),库必须位于APK的jniLibs目录或系统库路径中
  • 在类的静态初始化块中加载,加载时机是在类初始化时
  • 只需指定库名称(不含前缀lib和后缀.so)
public class NativeHelper {// 静态加载方式static {try {System.loadLibrary("native-lib");} catch (UnsatisfiedLinkError e) {Log.e("JNI", "加载本地库失败: " + e.getMessage());}}public native String stringFromJNI();
}

1.2、动态加载

动态加载指的是在运行时根据需要手动加载本地库。有以下特点:、

  • 使用System.load()方法
  • 需要指定库的完整路径
  • 可以在任何时间点加载
  • 更适合插件化或动态功能模块的场景
public class NativeHelper {private boolean isLibLoaded = false;public void loadLibrary(String fullPath) {if (!isLibLoaded) {System.load(fullPath); // 动态加载,如 "/data/data/com.example/app_lib/libnative-lib.so"isLibLoaded = true;}}public native String stringFromJNI();
}

2、JNI函数的两种注册方式

2.1、静态注册(固定命名规则)

通过函数名自动关联Java native方法和本地实现,依赖固定的函数命名规则(Java_包名_类名_方法名,类名中的特殊字符(如 $)需转义),只在首次调用native方法时查找符号。

// Native 层(无需显式注册)
JNIEXPORT void JNICALL
Java_com_example_NativeHelper_helloFromJNI(JNIEnv *env, jobject thiz) {// 实现代码
}

2.2、动态注册(JNI_OnLoad)

加载库后立即调用JNI_OnLoad主动注册本地方法(RegisterNatives),在方法调用前就完成所有注册,更加灵活高效。不强制要求注册所有函数,即使实现了JNI_OnLoad,未注册的函数仍可通过静态注册规则被调用。

以下是使用JNI_OnLoad注册的一个简单的示例:

#include <jni.h>// 本地方法实现
jstring native_hello(JNIEnv *env, jobject thiz) {return (*env)->NewStringUTF(env, "Hello from dynamic registration!");
}// 方法映射表
static JNINativeMethod methods[] = {{"helloFromJNI", "()Ljava/lang/String;", (void *)native_hello}
};// JNI_OnLoad 实现
jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env;// 1. 获取JNI环境指针if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}// 2. 查找目标Java类jclass clazz = env->FindClass("com/example/NativeHelper");if (clazz == NULL) {return JNI_ERR;}// 3. 注册本地方法if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) {return JNI_ERR;}// 4. 返回使用的JNI版本return JNI_VERSION_1_6;
}

对代码中内容做一点解释:

  • JavaVM 指针:
    • JavaVM是JNI提供的虚拟机接口,全局唯一,在整个进程生命周期内有效;
    • 由系统在调用JNI_OnLoad时传入,用于获取当前线程的JNIEnv
  • JNIEnv:
    • JNIEnv是一个指向JNI函数表的指针,包含了所有JNI接口函数(如FindClass、NewStringUTF等)。因此,在使用任何JNI功能前(如注册 Native 方法、查找类),必须先获取JNIEnv
    • JNIEnv是线程局部的,不同线程的JNIEnv不同,因此必须为当前线程获取该指针
  • JNINativeMethod是一个结构体:
typedef struct {const char* name;      // Java中的方法名const char* signature; // 方法签名void*       fnPtr;     // 本地函数指针
} JNINativeMethod;

2.3、JNINativeMethod中的方法签名

方法签名的基本格式为:“(参数类型)返回类型”。参数类型分为三种:基本数据类型、引用数据类型,数组类型,分别有不同的签名方式。

多个参数直接拼接,如"(IJLjava/lang/String;)V"表示"void (int, long, String)"。

2.3.1、基本数据类型签名

Java类型JNI签名
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
voidV

2.3.2、引用数据类型签名

Java类型JNI签名
StringLjava/lang/String;
ObjectLjava/lang/Object;
Class<?>Ljava/lang/Class;
ThrowableLjava/lang/Throwable;

引用类型必须用"L"开头,并用";“结尾,如"Ljava/lang/String;”。

如果是内部类,需要用$,比如"Landroid/media/MediaCodec$CryptoInfo;"

2.3.3、数组类型签名

Java类型JNI签名
int[][I
String[][Ljava/lang/String;
int[][][[I
Object[][Ljava/lang/Object;

数组类型用"[“开头,如”[I"表示"int[]"。

2.4、JNI函数

本地方法的前两个参数具有固定的含义,它们由JVM自动传递,用于提供环境上下文和调用对象信息。

实例方法:

JNIEXPORT 返回类型 JNICALL
Java_包名_类名_方法名(JNIEnv *env, jobject thiz, ...) {// 实现代码
}
  • 第一个参数(JNIEnv *env):JNI 环境指针,用于调用 NI函数(如创建Java对象、调用Java方法等)。每个线程有独立的JNIEnv,不可跨线程传递。
  • 第二个参数(jobject thiz):对Java对象的引用,表示调用该方法的实例(相当于 Java中的this)。

静态方法:

JNIEXPORT 返回类型 JNICALL
Java_包名_类名_方法名(JNIEnv *env, jclass clazz, ...) {// 实现代码
}
  • 第一个参数(JNIEnv *env):JNI环境指针。
  • 第二个参数(jclass clazz):对Java类的引用,表示调用该静态方法的类(相当于 Java中的Class对象)。

3、常见的JNIEnv方法

JNI数据类型映射

Java类型JNI类型说明
booleanjboolean无符号8位
bytejbyte有符号8位
charjchar无符号16位(UTF-16)
shortjshort有符号16位
intjint有符号32位
longjlong有符号64位
floatjfloat32位浮点
doublejdouble64位浮点
voidvoid无返回值
int[]jintArray数组类型,其他基本类型数组类似
Stringjstring字符串
Objectjobject任意Java对象(包含自定义)
String[]jobjectArray字符串数组
Object[]jobjectArray任意Java对象数组

JNI的基础类型与C/C++原生类型无缝对接,可以像使用普通变量一样操作它们。只有在处理Java对象、字符串、数组等引用类型时,才需要调用JNIEnv提供的方法。

3.1、类和对象操作方法

以下方法用于类的查找、对象的创建以及方法和字段的访问

  • jclass FindClass(const char* name):能够查找Java类。
  • jclass GetObjectClass(jobject obj):可获取对象的类。
  • jmethodID GetMethodID(jclass clazz, const char* name, const char* sig):能获取方法ID。
  • void CallVoidMethod(jobject obj, jmethodID methodID, …):用于调用返回void类型的实例方法,此外根据返回值类型还有 CallIntMethod、CallObjectMethod等变体。
  • jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig):可获取静态方法 ID。
  • void CallStaticVoidMethod(jclass clazz, jmethodID methodID, …):用于调用静态方法,此外根据返回值类型还有CallStaticIntMethod、CallSTaticObjectMethod等变体。
  • jfieldID GetFieldID(jclass clazz, const char* name, const char* sig):能获取字段ID。
  • jint GetIntField(jobject obj, jfieldID fieldID):可获取 int 类型的实例字段,另外根据字段类型有不同变体GetObjectField、GetLongField 等。
  • jobject NewObject(jclass clazz, jmethodID methodID, …):用于创建新对象。
  • void SetIntField(jobject obj, jfieldID fieldID, jint value):设置实例字段值,有SetObjectField等变体

3.2、字符串操作方法

用于Java字符串与本地字符串的转换:

  • jstring NewStringUTF(const char* bytes):能创建 Java 字符串。
  • const char * GetStringUTFChars(jstring string, jboolean* isCopy):可获取字符串的 UTF-8 编码形式。
  • void ReleaseStringUTFChars(jstring string, const char* utf):用于释放字符串资源。

从本地字符串创建Java字符串,返回的是java堆上的对象(jstring),由JVM的垃圾回收机制管理,本地代码只需将jstring返回给Java层或传递给其他JNI方法,无需额外释放。

从Java字符串获取本地字符串,用完jstring之后必须配对调用ReleaseStringUTFChars,防止本地内存泄漏(当 JNI 复制字符串内容时),阻止 Java 字符串被 GC 回收(当 JNI 持有内部引用时)。必须复制通过 GetStringUTFChars获取的字符串才能在JNI资源释放后继续使用。

3.3、数组操作方法

这些方法用于数组的创建、访问和修改:

  • jintArray NewIntArray(jint length):可创建 int 类型的数组,此外还有 NewByteArray、NewObjectArray 等变体。
  • void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); 根据设置类型有不同变体
  • jint * GetIntArrayElements(jintArray array, jboolean* isCopy):能获取数组元素。
  • void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode):用于释放数组资源。
  • jsize GetArrayLength(jarray array):可获取数组长度。

3.4、全局引用和局部引用操作方法

用于管理对象引用:

  • jobject NewGlobalRef(jobject obj):可创建全局引用。
  • void DeleteGlobalRef(jobject obj):用于删除全局引用。
  • jobject NewLocalRef(jobject obj):能创建局部引用。
  • void DeleteLocalRef(jobject obj):用于删除局部引用。
  • jobject NewWeakGlobalRef(jobject obj):创建全局弱引用
  • void DeleteWeakGlobalRef(jobject obj):删除全局弱引用

在JNI中,绝大多数JNI函数创建的都是局部引用,只有NewGlobalRef和NewWeakGlobalRef会创建全局引用。

以下示例是局部引用:

jstring str = (*env)->NewStringUTF(env, "Hello");  // 创建局部引用
jobject obj = (*env)->NewObject(env, cls, ctor);   // 创建局部引用
jobject arr = (*env)->GetObjectField(env, obj, fieldID);  // 创建局部引用

一般来说局部引用会自动释放,如果重复使用某个变量,可以手动调用DeleteLocalRef。

要注意的是FindClass返回的jclass是局部引用,如果要长期使用需要转为全局引用:

static jclass stringClass; // 全局变量jclass localCls = env->FindClass("java/lang/String"); // 局部引用
stringClass = env->NewGlobalRef(localCls); // 转为全局引用

jmethodID和jfieldID不是对象引用,而是方法/字段的标识符,无需创建全局引用!

以MediaCodec为例:frameworks/base/media/jni/android_media_MediaCodec.cpp

创建JMediaCodec时,传入了jobject,将它设置为全局弱引用,

mObject = env->NewWeakGlobalRef(thiz);

之后直接获取JNIEnv,调用mObject的回调方法postEventFromNative

JNIEnv *env = AndroidRuntime::getJNIEnv();env->CallVoidMethod(mObject, gFields.postEventFromNativeID,EVENT_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj);

将JMediaCodec存储到java对象的字段中:

env->CallVoidMethod(thiz, gFields.setAndUnlockContextID, (jlong)codec.get());

我们实际在使用弱引用之前要使用以下两个方法判断是否被回收

if (env->IsSameObject(weakGlobalRef, NULL)) {// 弱全局引用已被回收
} else {// 弱全局引用仍有效
}jobject liveObj = (*env)->NewLocalRef(env, weakGlobalRef);
if (liveObj == NULL) {// 对象已被回收
}

相关文章:

  • React基础教程(13):路由的使用
  • HTML5 列表、表格与媒体元素、页面结构分析
  • Android全局网络监控最佳实践(Kotlin实现)
  • t015-预报名管理系统设计与实现 【含源码!!!】
  • Hive在实际应用中,如何选择合适的JOIN优化策略?
  • Hive的GROUP BY操作如何优化?
  • 使用vscode进行c/c++开发的时候,输出报错乱码、cpp文件本身乱码的问题解决
  • split_conversion将json转成yolo训练用的txt,在直接按照8:1:1的比例分成训练集,测试集,验证集
  • 实现一个免费可用的文生图的MCP Server
  • Spring Boot 中 @RequestParam 和 @RequestPart 的区别详解(含实际项目案例)
  • 长短期记忆(LSTM)网络模型
  • 408考研逐题详解:2009年第25题
  • GSR 手环能耗数据实测:STM32 与 SD NAND 的功耗优化成果
  • 智橙PLM与MES系统集成项目执行记录 智渤慧晟机械装备技术服务部 24.08
  • 华锐视点助力,虚拟旅游绽放更璀璨光彩​
  • 视频压制(Video Encoding/Compression)
  • 华为交换机S12708常用命令
  • html+css+js趣味小游戏~Treasure Arena多人竞技(附源码)
  • 杨传辉:构建 Data × AI 能力,打造 AI 时代的一体化数据底座|OceanBase 开发者大会实录
  • day024-网络基础-TCP与UDP、DNS
  • 网页设计有限公司/百度seo不正当竞争秒收
  • 企业报刊网站建设情况总结/360公司官网首页
  • 乐清网站只做/最新足球新闻头条
  • 凡科网站内容怎么做效果好/自助建站网站模板
  • wordpress做的网站吗/网站运营推广的方法有哪些
  • 男女做吃动态网站/成都seo正规优化