文章目录
I . Native 调用 Java 方法
II . JNIEnv *env 与 jobject instance
III . JavaVM *vm
IV . 局部引用 与 全局引用 分析
V . Native 调用 Java 方法 ( 主线程 )
VI . Native 调用 Java 方法 ( 子线程 )
VII . Java 层方法
VIII . C++ Java 调用助手类 ( JavaCallHelper.h 头文件 )
IX . C++ Java 调用助手类 ( JavaCallHelper.cpp )
X . Native 入口 C++ 方法
I . Native 调用 Java 方法
1 . 前置知识点 : 参考 【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 ) 博客内容 , 了解如何在 C++ 中调用 Java 方法 ;
2 . Native 调用 Java 方法 流程如下 :
① 获取函数签名 : 查找字节码文件 , 使用 javap 获取函数签名 ;
② 反射获取 Java 方法 : 通过调用 jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) 方法获取方法 ID ;
③ 调用 Java 方法 : 通过调用 void CallXxxMethod(jobject obj, jmethodID methodID, …) 方法 , 调用 Java 方法 ;
II . JNIEnv *env 与 jobject instance
1 . 调用 Java 方法所需参数 : 调用 Java 方法需要 JNIEnv *env 参数 和 对应的 jobject instance Java 类参数 ;
① JNIEnv *env : JNI 环境 , 注意子线程的 JNI 环境需要获取 , 主线程的 JNI 环境可以直接从 Native 层实现的 Java 方法中获取 ;
② jobject instance : 在 Native 层的 Java 对象 ;
2 . 主线程 JNIEnv *env 和 jobject instance 获取方法 : 这两个值都可以在 C++ 中实现的 native 方法中获取 ;
extern "C" JNIEXPORT void JNICALL Java_kim_hsl_ffmpeg_Player_native_1prepare(JNIEnv *env, jobject instance, jstring dataSource_){ ... }
上面的 C++ 方法是实现的 kim.hsl.ffmpeg.Player 类的 native void native_prepare(String dataSource) 方法 ;
3 . 子线程 JNIEnv *env 获取方法 : 需要使用 JavaVM *vm 获取 , 即 Java 虚拟机参数 ; 获取流程如下 :
① 声明子线程 JNIEnv* 指针 ;
② Java 虚拟机 调用附加线程的方法 ;
//子线程 : 需要通过 JavaVM * 获取该子线程的 JNIEnv * JNIEnv *env_thread; //Java 虚拟机 调用附加线程的方法 , 可以获取当前线程的 JNIEnv* 指针 vm->AttachCurrentThread(&env_thread, 0);
III . JavaVM *vm
JavaVM *vm 获取方法 : 在 JNI_OnLoad() 方法中获取 ;
//JNI_OnLoad 中获取的 Java 虚拟机对象放在这里 JavaVM *javaVM; int JNI_OnLoad(JavaVM *vm, void *r){ javaVM = vm; return JNI_VERSION_1_6; }
JNI_OnLoad 参考 : 【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives ) II . JNI_OnLoad 方法
IV . 局部引用 与 全局引用 分析
1 . 局部引用 与 全局引用 : JavaVM *vm , JNIEnv *env 与 jobject instance 是在方法中获取的 , 如果跨线程调用 , 就需要考虑其引用的类型 , 局部引用 或 全局引用 ;
① 局部引用 : 方法结束后便不能使用了 ;
② 全局引用 : 可以跨方法 , 跨线程调用 ;
2 . 全局引用 : JNIEnv *env 与 JavaVM *vm 本身就是全局引用 , 不用刻意将其转为全局引用 , 可以跨方法跨线程调用 ;
3 . 局部引用 : jobject instance 是 Java_kim_hsl_ffmpeg_Player_native_1prepare 方法中的局部引用 , 如果要跨方法 , 跨线程调用 , 需要将其转为全局引用 ;
4 . 示例解析 : 在下面的构造方法中可以看到 , 针对 JNIEnv *env 与 JavaVM *vm , 没有经过任何处理 , 直接记录下来 , 就可以在其它任何方法 , 任何线程中调用 , 但是 jobject instance Java 对象 , 必须将其转为全局引用 , 才能在其它方法或线程中调用 ;
5 . 参考 :
① 局部引用 : 【Android NDK 开发】JNI 引用 ( 局部引用 | 局部引用作用域 | 局部引用产生 | 局部引用释放 | 代码示例)
② 全局引用 : 【Android NDK 开发】JNI 引用 ( 全局引用 | NewGlobalRef | DeleteGlobalRef )
③ 弱全局引用 : 【Android NDK 开发】JNI 引用 ( 弱全局引用 | NewWeakGlobalRef | DeleteWeakGlobalRef )
V . Native 调用 Java 方法 ( 主线程 )
主线程中可以直接使用 Native 方法中获取的 JNIEnv *env 调用 Java 方法 ;
//主线程 : 可以直接使用 JNIEnv * 指针 env->CallVoidMethod(instance, onErrorId, errorCode);
VI . Native 调用 Java 方法 ( 子线程 )
子线程需要通过 JavaVM * 获取该子线程的 JNIEnv * , 然后通过子线程的 JNIEnv * 调用 Java 方法 ;
//子线程 : 需要通过 JavaVM * 获取该子线程的 JNIEnv * JNIEnv *env_thread; //Java 虚拟机 调用附加线程的方法 , 可以获取当前线程的 JNIEnv* 指针 vm->AttachCurrentThread(&env_thread, 0); //调用 Java 方法 env_thread->CallVoidMethod(instance, onErrorId, errorCode); //解除线程附加 vm->DetachCurrentThread();
参考 : 【Android NDK 开发】JNI 线程 ( JNI 线程创建 | 线程执行函数 | 非 JNI 方法获取 JNIEnv 与 Java 对象 | 线程获取 JNIEnv | 全局变量设置 )
VII . Java 层方法
package kim.hsl.ffmpeg; import android.util.Log; /** * Java 层与 Native 层交互 接口 */ public class Player implements SurfaceHolder.Callback { private static final String TAG = "Player"; // 加载动态库 static { System.loadLibrary("native-lib"); } /** * C++ 层错误回调函数 * @param errorCode */ public void onError(int errorCode){ Log.i(TAG, "出现错误 错误码 : " + errorCode); } /** * C++ 中 prepare 时回调该方法 */ public void onPrepare(){ Log.i(TAG, "准备完毕 onPrepare"); } native void native_prepare(String dataSource); }
VIII . C++ Java 调用助手类 ( JavaCallHelper.h 头文件 )
// // Created by octop on 2020/3/2. // 作用 : 在 C/C++ 层调用 Java 层函数的帮助类 // 反射 Java 类 , 并调用其方法 // #ifndef INC_011_FFMPEG_JAVACALLHELPER_H #define INC_011_FFMPEG_JAVACALLHELPER_H #include <jni.h> class JavaCallHelper { public: //构造方法 JavaCallHelper(JavaVM *vm, JNIEnv *env, jobject instance); //析构方法 ~JavaCallHelper(); //错误回调方法 , 通过该方法回调错误信息给 Java 层 void onError(int thread, int errorCode); //准备回调方法 void onPrepare(int thread); private: /* * 跨线程相关 : * JNIEnv * 是不能跨线程使用的 * 如果在线程中反射调用 Java 方法 * 必须重新获取对应线程的 JNIEnv *env */ JavaVM *vm; JNIEnv *env; jobject instance; //onError 方法对应的 方法 ID jmethodID onErrorId; //onPrepare 方法对应的 方法 ID jmethodID onPrepareId; }; #endif //INC_011_FFMPEG_JAVACALLHELPER_H
IX . C++ Java 调用助手类 ( JavaCallHelper.cpp )
// // Created by octop on 2020/3/2. // #include "JavaCallHelper.h" JavaCallHelper::JavaCallHelper(JavaVM *vm, JNIEnv *env, jobject instance) { /* * 如果在子线程调用 Java 方方法 * 需要借助 JavaVM * vm , 获取子线程的 JNIEnv *env 进行反射调用 * * 如果在主线程调用 Java 方法 * 可以直接调用主线程传入的 JNIEnv *env 进行反射调用 * * 注意 : jobject 如果要跨方法 , 跨线程调用 , 需要创建全局引用 , 不要使用局部引用 */ this->vm = vm; this->env = env; this->instance = env->NewGlobalRef(instance); //初始化 onError 方法反射信息 jclass clazz = env->GetObjectClass(instance); //Java 中对应的方法 public void onError(int errorCode) this->onErrorId = env->GetMethodID(clazz, "onError", "(I)V"); //Java 中对应的 public void onPrepare() this->onPrepareId = env->GetMethodID(clazz, "onPrepare", "()V"); } JavaCallHelper::~JavaCallHelper() { //释放全局引用 env->DeleteGlobalRef(instance); } /** * 判断 thread 是否是主线程 * 如果是主线程 : * 如果是子线程 : * * * @param thread * @param errorCode */ void JavaCallHelper::onError(int thread, int errorCode) { if(thread == 1){ //主线程 : 可以直接使用 JNIEnv * 指针 this->env->CallVoidMethod(instance, onErrorId, errorCode); }else{ //子线程 : 需要通过 JavaVM * 获取该子线程的 JNIEnv * JNIEnv *env_thread; //Java 虚拟机 调用附加线程的方法 , 可以获取当前线程的 JNIEnv* 指针 vm->AttachCurrentThread(&env_thread, 0); //调用 Java 方法 env_thread->CallVoidMethod(instance, onErrorId, errorCode); //解除线程附加 vm->DetachCurrentThread(); } } void JavaCallHelper::onPrepare(int thread) { if(thread == 1){ //主线程 : 可以直接使用 JNIEnv * 指针 this->env->CallVoidMethod(instance, onPrepareId); }else{ //子线程 : 需要通过 JavaVM * 获取该子线程的 JNIEnv * JNIEnv *env_thread; //Java 虚拟机 调用附加线程的方法 , 可以获取当前线程的 JNIEnv* 指针 vm->AttachCurrentThread(&env_thread, 0); //调用 Java 方法 env_thread->CallVoidMethod(instance, onPrepareId); //解除线程附加 vm->DetachCurrentThread(); } }
X . Native 入口 C++ 方法
#include <jni.h> #include <string> #include "FFMPEG.h" //声明 FFMPEG 类 FFMPEG *ffmpeg = 0; //JNI_OnLoad 中获取的 Java 虚拟机对象放在这里 JavaVM *javaVM; int JNI_OnLoad(JavaVM *vm, void *r){ javaVM = vm; return JNI_VERSION_1_6; } extern "C" JNIEXPORT void JNICALL Java_kim_hsl_ffmpeg_Player_native_1prepare(JNIEnv *env, jobject instance, jstring dataSource_) { //创建 Java 调用类 JavaCallHelper * javaCallHelper = new JavaCallHelper(javaVM, env, instance); //调用 Java 层的 onPrepare 方法 callHelper->onPrepare(2); }