文章目录

  • 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 | 引用类型 | 模板代码示例 )相关推荐

  1. 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )

    文章目录 I . FFMPEG ANativeWindow 原生绘制 II . FFMPEG 原生绘制流程 III . Java 层获取 Surface 画布 IV . 传递 Surface 画布到 ...

  2. 安卓开发——JNI——回调java中的方法

    JNI开发中 在C代码中回调java中的方法 package com.example.jnitest2;import android.app.Activity; import android.cont ...

  3. 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )

    文章目录 I . FFMPEG ANativeWindow 原生绘制 前置操作 II . FFMPEG 原生绘制流程 III . 设置 ANativeWindow 绘制窗口属性 ANativeWind ...

  4. 【Android FFMPEG 开发】音视频基础 和 FFMPEG 编译 ( 音视频基础 | MPEG-4 标准 | Android 开发环境 | FFMPEG 交叉编译 | 安卓项目导入配置 )

    本篇博客代码及资源下载 : https://download.csdn.net/download/han1202012/10382762 文章目录 一. 音视频基础 1. 音频基础 (1) 声音要素 ...

  5. JNI中创建新的线程回调java方法的技巧

    在实际项目中,经常需要在Native层创建新的线程处理一些耗时操作,然后将结果回调给java层.如果按照普通的方式,直接获取MethodID,然后新线程中调用CallxxxMethod(),这样肯定是 ...

  6. 【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )

    文章目录 I . FFMPEG 播放视频流程 II . OpenSLES 播放音频流程 III . OpenSLES 播放参考 Google 官方示例 IV . OpenSL ES 播放代码 ( 详细 ...

  7. 【Android FFMPEG 开发】Android 中使用 FFMPEG 对 MP3 文件进行混音操作

    文章目录 一.前置操作 ( 移植 FFMPEG ) 二.FFMPEG 混音命令 三.Android FFMPEG 混音源代码完整示例 四.博客源码 一.前置操作 ( 移植 FFMPEG ) 参考 [A ...

  8. 【Android FFMPEG 开发】FFMPEG 获取编解码器 ( 获取编解码参数 | 查找编解码器 | 获取编解码器上下文 | 设置上下文参数 | 打开编解码器 )

    文章目录 博客简介 . FFMPEG 编解码器获取流程 I . FFMPEG 获取音视频流的编解码参数 AVCodecParameters *codecpar II . FFMPEG 查找解码器 av ...

  9. 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )

    文章目录 I . FFMPEG 播放视频流程 II . FFMPEG 音频重采样流程 III . FFMPEG 音频重采样 IV . FFMPEG 初始化音频重采样上下文 SwrContext V . ...

最新文章

  1. python答题系统的代码_Python考试系统自动答题(教务处)
  2. R语言PCA主成分分析(Principle Component Analysis)实战2
  3. springboot + shiro 尝试登录次数限制与并发登录人数控制
  4. 单体应用架构——垂直应用架构———分布式架构———SOA架构———微服务架构
  5. 安装bigdesk后es无法启动_安装天正后,探索者无法双击启动?
  6. MATLAB(五)在线性代数中的应用
  7. OpenCV中的仿射变换
  8. Win10怎么打开或关闭自动维护功能
  9. 我们常说的CDN到底是什么?
  10. 约瑟夫问题 c语言数组,约瑟夫问题的数组实现
  11. 计算机文化基础(高职高专版 第十一版)第一章答案
  12. 下个五年,跨境支付的变数在哪里?
  13. eclipse hadoop1.2.0配置及wordcount运行
  14. ios王者荣耀更新服务器维护,王者荣耀苹果更新不了 苹果无法进行版本更新如何解决...
  15. JSP入门教程:JSP简明教程
  16. 张一鸣宣布卸任字节跳动CEO
  17. 分布式计算框架(四) 计算节点模块
  18. 软件从业者成功的秘密
  19. amdgpu_fence_emit 的原罪-- 完成fence
  20. mysql proxies priv_Mysql 5.7.18 运用MySQL proxies_priv完成类似用户组管理案例分享

热门文章

  1. 防火墙术语详解(一)
  2. Spring Cloud学习系列第六篇【分布式配置中心】
  3. 一类SG函数递推性质的深入分析——2018ACM陕西邀请赛H题
  4. Java中变量、类初始化顺序
  5. Hive常用函数大全一览
  6. python入门第二天__练习题
  7. 1008 N的阶乘 mod P ——51Nod(同余定理)
  8. 关于开源堡垒机Jumpserver二次开发
  9. 【剑指offer】Q38:数字在数组中出现的次数
  10. ExtJS 开发调试工具大全