【Android FFMPEG 开发】C++ 回调 Java 方法 模板 ( JavaVM *vm | JNIEnv *env | jobject instance | 引用类型 | 模板代码示例 )

简介: 【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 对象 , 必须将其转为全局引用 , 才能在其它方法或线程中调用 ;

image.png




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);
}



目录
相关文章
|
9月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1662 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
9月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1100 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
9月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
417 0
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
560 12
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
298 0
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
473 0
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
292 16
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。