Android C++系列:JNI调用 Java 类的构造方法和父类的方法

简介: Android JNI开发时经常遇到C/C++层访问Java层对象的,比如C/C++层创建一个String返回,或者访问Java层提供的MediaCodec等,此时我们就需要通过 JNI 来调用 Java 一个类的构造方法来创建这个 Java 类。

image.png


Android JNI开发时经常遇到C/C++层访问Java层对象的,比如C/C++层创建一个String返回,或者访问Java层提供的MediaCodec等,此时我们就需要通过 JNI 来调用 Java 一个类的构造方法来创建这个 Java 类。


调用构造方法


构造方法是特殊的类方法,但是调用构造方法和之前调用类的实例方法步骤类似,也需要获得对应的类的jclass和方法 id。


  • 对于类,通过 FindClass 可以找到对应的 Java 类型。


  • 对于构造方法,通过 GetMethodID 方法来获取它的方法ID,不同的是构造方法对应的名称为<init> ,返回值类型是 void 。


  • 通过 NewObject传入获取到的构造方法id 来调用构造方法创建具体的类。


下面以 String 的字符数组构造方法为例在C/C++层实现对象的创建。


public String(char value[])


对应的 C++ 代码:


extern "C"
 JNIEXPORT jstring JNICALL
 Java_com_qingkouwei_demo_InvokeDemo_invokeStringConstructors(JNIEnv *env, jobject instance) {
     jclass stringClass;
     jmethodID cid;
     jcharArray elemArr;
     jstring result;
     // 创建string字符串
     jstring temp = env->NewStringUTF("this is char array");
     // jstring字符串转换为字节数组,作为构造Java String的参数
     const jchar *chars = env->GetStringChars(temp, NULL);
     int len = 10;
     stringClass = env->FindClass("java/lang/String"); // 找到具体的 String 类
     if (stringClass == NULL) {
         return NULL;//容错
     }
     // 找到具体的方法,([C)V 表示选择 String 的 String(char value[]) 构造方法
     cid = env->GetMethodID(stringClass, "<init>", "([C)V");
     if (cid == NULL) {
         return NULL;//容错
     }
     // 字符串数组作为参数
     elemArr = env->NewCharArray(len);
     if (elemArr == NULL) {
         return NULL;
     }
     // 给字符串数组赋值
     env->SetCharArrayRegion(elemArr, 0, len, chars);
     // 使用NewObject创建类
     result = (jstring) env->NewObject(stringClass, cid, elemArr);
     env->DeleteLocalRef(elemArr);
     env->DeleteLocalRef(stringClass);
     return result;
 }


我们先构造好字符数组,并赋值,再获取String的jclass以及构造方法的方法id,通过NewObject传入字符数组来调用Sting 的构造函数创建String对象。


其他的不管是Android系统提供的类还是自定义的各种Java类,都可以通过上述流程来创建。


再来看一个调用自定义类的构造方法的示例,还是之前的 Animal 类,它的构造方法有一个 String 类型的参数。


NewObject其实是一个方法做了两件事情:


  1. 创建类对象;
  2. 调用构造方法。


Jni提供了AllocObject 创建对象和CallNonvirtualVoidMethod调用构造方法来分步完成Java对象的构建,以我们自定义的一个简单类为例:


/**
  * 通过 AllocObject 方法来创建一个类
  */
 extern "C"
 JNIEXPORT jobject JNICALL
 Java_com_qingkouwei_demo_InvokeDemo_allocObjectConstructor(JNIEnv *env, jobject instance) {
     jclass fruitClass;
     jobject result;
     jmethodID mid;
     // 获得对应的Fruit类
     fruitClass = env->FindClass("com/qingkouwei/demo/bean/Fruit");
     if (fruitClass == NULL) {//为空容错判断
         return NULL;
     }
     // 获得Fruit构造方法id
     mid = env->GetMethodID(fruitClass, "<init>", "(Ljava/lang/String;)V");
     if (mid == NULL) {//为空容错判断
         return NULL;
     }
     // 构造方法的参数
     jstring args = env->NewStringUTF("creat fruit use AllocObject");
     // 创建未被初始化的对象
     result = env->AllocObject(fruitClass);
     if (result == NULL) {//为空容错判断
         return NULL;
     }
     //使用CallNonvirtualVoidMethod 方法调用类的构造方法
     env->CallNonvirtualVoidMethod(result, fruitClass, mid, args);
     if (env->ExceptionCheck()) {//检测创建过程中是否有异常
         env->DeleteLocalRef(result);
         return NULL;
     }
     return result;
 }


这里实现了对象创建和初始化分离的实现,在Java中没有为我们提供的方法,在JNI层提供了。


调用父类的方法


我们知道Java的一大特定是多态,类是可以继承或者被继承的,那么问题来了,在JNI中创建Java对象时如何调用父类的方法呢?


我们可以在子类中通过调用 CallNonvirtualMethod 方法来调用父类的方法。


构造一个相应的子类,然后获得父类的 类型和方法 id,根据父类方法的返回值选择调用不同的 CallNonvirtualMethod函数:


  1. 对于引用类型的,调用 CallNonvirtualObjectMethod 方法;
  2. 对于基础类型的,调用 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等等;
  3. 对于无返回值类型的,调用 CallNonvirtualVoidMethod 方法。


我们实现一个在苹果中调用水鬼的getName的方法:


/**
  * JNI层中创建子类并调用父类方法
  */
 extern "C"
 JNIEXPORT void JNICALL
 Java_com_qingkouwei_demo_Demo_InvokeDemo_callSuperMethod(JNIEnv *env, jobject instance) {
     jclass apple_cls; // Apple 类的类型
     jmethodID apple_cid; // Apple 类的构造方法 id
     jstring apple_name; // Apple 类的构造方法参数
     jobject apple;
     // 获得对应的 类
     apple_cls = env->FindClass("com/qingkouwei/demo/bean/Apple");
     if (apple_cls == NULL) {
         return;
     }
     // 获得构造方法 id
     apple_cid = env->GetMethodID(apple_cls, "<init>", "(Ljava/lang/String;)V");
     if (apple_cid == NULL) {
         return;
     }
     // 准备构造方法的参数
     apple_name = env->NewStringUTF("this is apple name");
     // 创建 Apple 类
     apple = env->NewObject(apple_cls, apple_cid, apple_name);
     if (apple == NULL) {
         return;
     }
     //调用父类的 getName 参数
     jclass fruit_cls; // 父类的类型
     jmethodID fruit_mid; // 被调用的父类的方法 id
     // 获得父类对应的类
     fruit_cls = env->FindClass("com/qingkouwei/demo/bean/Fruit");
     if (fruit_cls == NULL) {
         return;
     }
     // 获得父类被调用的方法 id
     fruit_mid = env->GetMethodID(fruit_cls, "getName", "()Ljava/lang/String;");
     if (fruit_mid == NULL) {
         return;
     }
     jstring name = (jstring) env->CallNonvirtualObjectMethod(apple, fruit_cls, fruit_mid);
     if (name == NULL) {
         return;
     }
     //print
     LOGI("getName method value is %s", env->GetStringUTFChars(name, NULL));
 }


Apple作为Fruit的子类,首先通过NewObject 构建子类Apple对象,然后再获取父类Fruit的jclass以及方法id,通过CallNonvirtualObjectMethod调用父类Fruit的getName方法。


小结


本文讲解了JNI层创建Java层对象的两种方法(通过NewObject一次性创建和通过AllocObject 和CallNonvirtualVoidMethod分布创建的方法)和如何在JNI层调用Java层类对象的父类方法的方法。对一些复杂的项目使用JNI特性提供了一些思路。

目录
相关文章
|
7天前
|
Java
java构造方法,构造代码块,静态代码块的执行顺序
本文介绍了Java中构造方法、构造代码块和静态代码块的执行顺序。静态代码块用`static`声明,在JVM加载类时执行一次;构造代码块在每次创建对象时执行,先于构造方法;构造方法用于对象初始化,创建对象时调用。示例代码展示了这三者的输出顺序,并解释了它们的区别和应用场景。
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
|
4月前
|
Java 程序员 数据库连接
Java执行顺序大揭秘:静态块、非静态块和构造方法谁先谁后?
本文详细介绍了Java中的初始化块,包括静态初始化块和非静态初始化块的概念、执行顺序和实际应用场景。通过具体示例,帮助读者理解这两种初始化块的区别和使用场景,让面试官对你刮目相看。
56 0
Java执行顺序大揭秘:静态块、非静态块和构造方法谁先谁后?
WK
|
4月前
|
安全 Java 编译器
C++和Java哪个更好用
C++和Java各具优势,选择取决于项目需求、开发者偏好及目标平台特性。C++性能出色,适合游戏、实时系统等;Java平台独立性强,适合跨平台、安全敏感应用。C++提供硬件访问和灵活编程范式,Java有自动内存管理和丰富库支持。两者各有千秋,需根据具体需求选择。
WK
121 1
|
5月前
|
IDE Java 程序员
C++ 程序员的 Java 指南
一个 C++ 程序员自己总结的 Java 学习中应该注意的点。
44 5
|
4月前
|
Java
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
|
5月前
|
设计模式 Java 测试技术
Java零基础-构造方法详解
【10月更文挑战第5天】Java零基础教学篇,手把手实践教学!
66 1
|
5月前
|
Java
java构造方法时对象初始化,实例化,参数赋值
java构造方法时对象初始化,实例化,参数赋值
142 1
|
5月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
80 1
|
5月前
|
缓存 并行计算 Java
C++矢量运算与java矢量运算
本文探讨了C++和Java中的矢量运算与标量运算的性能比较,解释了矢量运算的原理和为什么它比标量运算快,包括并行性、数据局部性、指令优化和数据重用等优势。文章还提供了C++和Java的矢量运算示例代码,并展示了运行结果,以证明矢量运算在处理大量数据时的性能优势。
51 0
C++矢量运算与java矢量运算

热门文章

最新文章

  • 1
    Android历史版本与APK文件结构
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 3
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 4
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 5
    【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
  • 6
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 7
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
  • 8
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
  • 9
    Android实战经验之Kotlin中快速实现MVI架构
  • 10
    即时通讯安全篇(一):正确地理解和使用Android端加密算法