JNI技术之手写JNIEnv与静态缓存与native异常

借助NDK数组排序

MainActivity

java 复制代码
public void sortAction(){
        int[] arr = new int[]{11,22,-3,2,4,6,-15};
        sort(arr);
        for (int j : arr) {
            Log.e("cyr", "sortAction:" + j);
        }
    }

native-lib.cpp

cpp 复制代码
int compare(const jint * number1, const jint * number2) {
    return *number1 - *number2;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_as_1jni_1project_MainActivity_sort(JNIEnv *env, jobject thiz, jintArray arr) {
    jint * intArray = env->GetIntArrayElements(arr, nullptr);
    int length = env->GetArrayLength(arr);
    qsort(intArray, length, sizeof(int),
          reinterpret_cast<int (*)(const void *, const void *)>(compare));

    env->ReleaseIntArrayElements(arr,intArray,0);
}

输出日志可以看到

bash 复制代码
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:-15
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:-3
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:2
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:4
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:6
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:11
2026-03-26 15:29:32.843 27549-27549 cyr                     com.example.as_jni_project           E  sortAction:22

静态缓存

MainActivity2

java 复制代码
package com.derry.as_jni_project;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
// TODO 02.静态缓存
public class MainActivity2 extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
static {
System.loadLibrary("native-lib");
}
//假设这里定义了一大堆变量
static String name1 = "T1";
static String name2 = "T2";
static String name3 = "T3";
static String name4 = "T4";
static String name5 = "T5";
static String name6 = "T6";
//省略更多....
public static native void localCache(String name); //普通局部缓存弊端演示
//下面是静态缓存区域
public static native void initStaticCache(); //初始化静态缓存
public static native void staticCache(String name); //静态缓存
public static native void clearStaticCache(); //清除静态缓存
/**
*静态缓存策略
*/
public void staticCacheAction(View view) {
//下面是局部缓存的演示
localCache("李元霸");
Log.e("Derry", "name1:" + name1);
// TODO下面是静态缓存区域==============================
initStaticCache(); //先初始化静态缓存(注意:如果是一个类去调用,就需要在构造函数中初始化)
staticCache("李白"); //再执行...
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
staticCache("李小龙");
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
staticCache("李连杰");
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
staticCache("李贵");
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
staticCache("李逵");
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
staticCache("李鬼");
Log.e("Derry", "静态缓存区域name1:" + name1);
Log.e("Derry", "静态缓存区域name2:" + name2);
Log.e("Derry", "静态缓存区域name3:" + name3);
Log.e("Derry", "静态缓存区域name4:" + name4);
Log.e("Derry", "静态缓存区域name5:" + name5);
Log.e("Derry", "静态缓存区域name6:" + name6);
}
@Override
protected void onDestroy() {
super.onDestroy();
clearStaticCache(); //必须要清除静态缓存
}
}

native-lib.cpp

cpp 复制代码
//普通局部缓存弊端演示
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity2_localCache(JNIEnv *env, jclass clazz, jstring
name) {
//像OpenCV WebRtc等大量使用局部缓存
// name属性赋值操作
static jfieldID f_id = nullptr; //局部缓存,这个方法会被多次调用,不需要反复的去获取jfieldID
if (f_id == nullptr) {
f_id = env->GetStaticFieldID(clazz, "name1", "Ljava/lang/String;");
}else {
LOGD("fieldID是空的啊");
}
env->SetStaticObjectField(clazz, f_id, name);
}
// TODO ======================下面是全局静态缓存区域
//全局静态缓存,在构造函数中初始化的时候会去缓存
static jfieldID f_name1_id = nullptr;
static jfieldID f_name2_id = nullptr;
static jfieldID f_name3_id = nullptr;
static jfieldID f_name4_id = nullptr;
static jfieldID f_name5_id = nullptr;
static jfieldID f_name6_id = nullptr;
// 1先初始化静态缓存(类似于在构造方法里面先初始化缓存)
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity2_initStaticCache(JNIEnv *env, jclass clazz) {
//初始化全局静态缓存
f_name1_id = env->GetStaticFieldID(clazz, "name1", "Ljava/lang/String;");
f_name2_id = env->GetStaticFieldID(clazz, "name2", "Ljava/lang/String;");
f_name3_id = env->GetStaticFieldID(clazz, "name3", "Ljava/lang/String;");
f_name4_id = env->GetStaticFieldID(clazz, "name4", "Ljava/lang/String;");
f_name5_id = env->GetStaticFieldID(clazz, "name5", "Ljava/lang/String;");
f_name6_id = env->GetStaticFieldID(clazz, "name6", "Ljava/lang/String;");
//省略....
}
// 2然后再执行时,不会重复的去获取jfieldID
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity2_staticCache(JNIEnv *env, jclass clazz, jstring
name) {
//如果这个方法会反复的被调用,那么不会反复的去获取jfieldID,因为是先初始化静态缓存,然后再执行此函数的
env->SetStaticObjectField(clazz, f_name1_id, name);
env->SetStaticObjectField(clazz, f_name2_id, name);
env->SetStaticObjectField(clazz, f_name3_id, name);
env->SetStaticObjectField(clazz, f_name4_id, name);
env->SetStaticObjectField(clazz, f_name5_id, name);
env->SetStaticObjectField(clazz, f_name6_id, name);
}
// 3最后要清除静态缓存
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity2_clearStaticCache(JNIEnv *env, jclass clazz) {
f_name1_id = nullptr;
f_name2_id = nullptr;
f_name3_id = nullptr;
f_name4_id = nullptr;
f_name5_id = nullptr;
f_name6_id = nullptr;
LOGD("静态缓存清除完毕...");
}

native异常捕获

MainActivity3:

java 复制代码
package com.derry.as_jni_project;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.nio.file.NoSuchFileException;
// TODO 03.native异常捕获
public class MainActivity3 extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
static {
System.loadLibrary("native-lib");
}
//这里定义变量
static String name1 = "T1";
//下面是异常处理
public static native void exception();
public static native void exception2() throws NoSuchFieldException;
public static native void exception3();
/**
* native异常捕获
*/
public void exceptionAction(View view) {
exception();
try {
exception2();
} catch (NoSuchFieldException e) {
e.printStackTrace();
Log.d("Derry", "Java层的exception2异常被我捕获到了...");
}
exception3();
}
//此函数是让native层来调用的函数
public static void show() throws Exception {
Log.d("Derry", "show: 111");
Log.d("Derry", "show: 222");
Log.d("Derry", "show: 333");
Log.d("Derry", "show: 444");
Log.d("Derry", "show: 555");
throw new NullPointerException("我是java中的抛出的异常,我的show方法里面发送了Java语法错
误");
}
}

native-lib.cpp

cpp 复制代码
// TODO 03.native异常捕获======================
//异常方式一:【C++处理时异常】扭转乾坤
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity3_exception(JNIEnv *env, jclass clazz) {
//假设现在想操作name999,没有name999就会在native层奔溃掉
jfieldID f_id = env->GetStaticFieldID(clazz, "name999", "Ljava/lang/String;");
//共两种方式之方式一
//补救措施,name999拿不到报错的话,那么我就拿name0
jthrowable throwable = env->ExceptionOccurred(); //检测本次函数执行,到底有没有异常
if (throwable){
//补救措施,先把异常清除
LOGD("native层:检测到有异常...");
//清除异常
env->ExceptionClear();
//重新获取name1属性
f_id = env->GetStaticFieldID(clazz, "name1", "Ljava/lang/String;");
}
}
//异常方式二:【C++处理时异常】往Java层抛出异常
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity3_exception2(JNIEnv *env, jclass clazz) {
//假设现在想操作name999,没有name999就会在native层奔溃掉
jfieldID f_id = env->GetStaticFieldID(clazz, "name999", "Ljava/lang/String;");
//共两种方式之方式二
//补救措施,name999拿不到报错的话,想给java层抛一个异常
jthrowable throwable = env->ExceptionOccurred();//检测本次函数执行,到底有没有异常
//想给java层抛一个异常
if (throwable){
//清除异常
env->ExceptionClear();
// Throw抛一个java的Throwable对象
jclass no_such_clz = env->FindClass("java/lang/NoSuchFieldException");
env->ThrowNew(no_such_clz,"NoSuchFieldException实在是找不到name999啊,没办法,奔溃了!");
}
}
//异常方式三:【Java处理时异常】Java的方法抛出了异常,然后native去清除
//注意:Java的异常native层无法捕获
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity3_exception3(JNIEnv *env, jclass clazz) {
jmethodID showMID = env->GetStaticMethodID(clazz, "show", "()V");
env->CallStaticVoidMethod(clazz, showMID);
//按道理来说,上面的这句话:env->CallStaticVoidMethod(clazz, showMID);,就已经奔溃了,但是事实是
否如此呢?
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.1");
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.2");
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.3");
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.4");
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.5");
LOGI("native层:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.6");
//证明不是马上就奔溃了,而是预料了时间,给我们处理呀
if (env->ExceptionCheck()) {
env->ExceptionDescribe();//输出描述
env->ExceptionClear();//清除异常
}
}
// TODO JNI异常处理总结:==============================================
/*
Native层出错了,没有办法再Java层去try的,处理的方式一般有两种:
第一种:补救,例如:name999获取检测到发生异常了,就再去获取name0
第二种:抛出,例如:name999获取检测到发生异常了,把此异常抛给Java层,让Java层去捕获异常
Java层出错了,Native层可以去监测到然后清除Java的异常,具体业务逻辑自己去处理哦
*/

C++异常简介

T1.cpp

cpp 复制代码
// TODO C++异常
#include <iostream>
#include <string>
using namespace std;
void exceptionMethod01()
{
throw "我报废了";
}
//更加简单的写法自定义异常
class Student {
public:
char * getInfo() {
return "自定义";
}
};
void exceptionMethod02() {
Student s;
throw s;
}
int main()
{
try {
exceptionMethod01();
} catch (const char * &msg) {
cout << "捕获到异常信息:" << msg << endl;
}
try {
exceptionMethod02();
} catch (Student &s) {
cout << "捕获到异常信息:" << s.getInfo() << endl;
}
return 0;
}

手写JNIEnv

cpp 复制代码
// TODO手写JNIEnv
#include <iostream>
#include <string>
using namespace std;
//如果是C语言
typedef const struct JNINativeInterface * JNIEnv; //定义一个结构体指针的别名
//模拟一个结构体
struct JNINativeInterface{
//结构体的函数指针
char*(*NewStringUTF)(JNIEnv*, char*); //函数指针的定义,实现在库中,我们这里还看不到
//省略300多个函数指针
// ...
};
typedef char * jstring; //简化了
typedef char * jobject; //简化了
//函数指针对应的函数实现(这里只是简写,真正复杂的代码,就不去考虑了)
jstring NewStringUTF(JNIEnv* env, char* c_str){
//注意:在真正的源码中,这里需要写很多复杂代码来转换的(毕竟涉及到跨语言操作了C<-->Java),这里我们
就简写了
// c_str -> jstring
return c_str;
}
//模拟我们的JNI函数,重点关注形参JNIEnv * env
char* Java_j07_Demo02_getCStringPwd(JNIEnv * env, jobject jobject){
// JNIEnv *其实已经是一个二级指针了,所以->调用的情况下必须是一级指针*取值
//C语言就是,就是二级指针,所以需要*取出一级指针才能使用->,->代表操作一级指针
return (*env)->NewStringUTF(env, "9527");
}
//下面是测试端--其实就是模拟JNIEnv内部执行的过程
int main() {
//构建JNIEnv*对象
struct JNINativeInterface nativeInterface;
//给结构方法指针进行赋值(实现)
nativeInterface.NewStringUTF = NewStringUTF;
//传给Java_j07_Demo02_getCStringPwd的参数是JNIEnv*
JNIEnv env = &nativeInterface;//一级指针
JNIEnv * jniEnv = &env;//二级指针
//把jniEnv对象传给Java_j07_Demo02_getCStringPwdJava层
char* jstring = Java_j07_Demo02_getCStringPwd(jniEnv, "com/derry/jni/MainActivity");
// jstring通过JNIEnv桥梁传给java层(这个过程也省略了...直接打印了)
printf("Java层就拿到了C++给我们的jstring = %s", jstring);
return 0;
}
相关推荐
lifallen2 小时前
Flink Agents:Python 执行链路与跨语言 Actor (PyFlink Agent)
java·大数据·人工智能·python·语言模型·flink
飞翔的SA2 小时前
全程 Python:无需离开 Python 即可实现光速级 CUDA 加速,无需c++支持
开发语言·c++·python·nvidia·cuda
常利兵2 小时前
Spring Boot配置diff:解锁配置管理新姿势
java·spring boot·后端
小臭希2 小时前
Git(代码版本控制系统)
java·git·github
SccTsAxR2 小时前
算法进阶:贪心策略证明全攻略与二进制倍增思想深度解析
c++·经验分享·笔记·算法
北风toto2 小时前
java进制转换方法
java·开发语言·python
2301_792674862 小时前
java学习day27(算法)
java·学习·算法
CoderMeijun2 小时前
CMake 入门笔记
c++·笔记·编译·cmake·构建工具
楼田莉子2 小时前
设计模式:创建型设计模式简介
服务器·开发语言·c++·设计模式