【Android FFMPEG 开发】C++ 回调 Java 方法 模板 ( JavaVM *vm | JNIEnv *env | jobject instance | 引用类型 | 模板代码示例 )
文章目錄
- 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 方法對應的 方法 IDjmethodID onErrorId;//onPrepare 方法對應的 方法 IDjmethodID 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); }
總結
以上是生活随笔為你收集整理的【Android FFMPEG 开发】C++ 回调 Java 方法 模板 ( JavaVM *vm | JNIEnv *env | jobject instance | 引用类型 | 模板代码示例 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android FFMPEG 开发】A
- 下一篇: 【Android FFMPEG 开发】F