文章目录
代码语言:txt复制 - [I . Native 调用 Java 方法](https://cloud.tencent.com/developer)
- [II . JNIEnv *env 与 jobject instance](https://cloud.tencent.com/developer)
- [III . JavaVM *vm](https://cloud.tencent.com/developer)
- [IV . 局部引用 与 全局引用 分析](https://cloud.tencent.com/developer)
- [V . Native 调用 Java 方法 ( 主线程 )](https://cloud.tencent.com/developer)
- [VI . Native 调用 Java 方法 ( 子线程 )](https://cloud.tencent.com/developer)
- [VII . Java 层方法](https://cloud.tencent.com/developer)
- [VIII . C Java 调用助手类 ( JavaCallHelper.h 头文件 )](https://cloud.tencent.com/developer)
- [IX . C Java 调用助手类 ( JavaCallHelper.cpp )](https://cloud.tencent.com/developer)
- [X . Native 入口 C 方法](https://cloud.tencent.com/developer)
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 方法中获取 ;
代码语言:javascript复制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 虚拟机 调用附加线程的方法 ;
代码语言:javascript复制 //子线程 : 需要通过 JavaVM * 获取该子线程的 JNIEnv *
JNIEnv *env_thread;
//Java 虚拟机 调用附加线程的方法 , 可以获取当前线程的 JNIEnv* 指针
vm->AttachCurrentThread(&env_thread, 0);
III . JavaVM *vm
JavaVM *vm 获取方法 : 在 JNI_OnLoad() 方法中获取 ;
代码语言:javascript复制//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 方法 ;
代码语言:javascript复制 //主线程 : 可以直接使用 JNIEnv * 指针
env->CallVoidMethod(instance, onErrorId, errorCode);
VI . Native 调用 Java 方法 ( 子线程 )
子线程需要通过 JavaVM * 获取该子线程的 JNIEnv * , 然后通过子线程的 JNIEnv * 调用 Java 方法 ;
代码语言:javascript复制 //子线程 : 需要通过 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 层方法
代码语言:javascript复制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 头文件 )
代码语言:javascript复制//
// 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 )
代码语言:javascript复制//
// 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 方法
代码语言:javascript复制#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);
}