文档章节

Android JNI开发系列(十一) JNI 访问父类的构造方法和父类实例方法

蔡小鹏
 蔡小鹏
发布于 2018/10/17 08:44
字数 1625
阅读 448
收藏 3

JNI 访问父类的构造方法和父类实例方法

构造方法和父类实例方法

先看一段Java代码,

Java package org.professor.jni.animal;

import android.util.Log;

public class Animal {

	protected String name;
	
	public Animal(String name) { this.name = name; }
	
	public String getName() { return name; }
	
	public void setName(String name) { this.name = name; }
	
	public void eat() { Log.i("ANIMAL", "ANIMAL EAT FOOD"); }

}

package org.professor.jni.animal;

import android.util.Log;

public class Bird extends Animal {

	public Bird(String name) { 
		super(name); 
		Log.i("ANIMAL", "BIRD Constructor"); 
	}
	
	@Override 
	public String getName() { 
		Log.i("ANIMAL", "BIRD Get Name Method"); 
		return name; 
	}
	
	@Override 
	public void eat() { 
		Log.i("ANIMAL", "BIRD EAT MI"); 
	} 

} 

简单看下上面代码,很简单的两个类,父类 Animal ,子类 Bird , Animal 类中定义了 eat()getName() 两个方法,Bird 继承自 Animal ,并重写了父类的两个方法。如果调用的话可以用 Animal bird = new Bird("Professor"); , bird.eat()在执行 new Bird("Professor")这段代码时, 会先为 Bird 类分配内存空间(所分配的内存空间大小由 Bird 类的成员变量数量决定),然后调用 Bird 的带参构造方法初始化对象。 Bird 是 Animal 类型,但它指向的是 Bird 实例对象的引用,而且 Bird 重写了父类的 eat 方法,因为调用 eat 方法时有多态存在,所以访问的是 Bird 的 eat 而非 Animal 的 eat,运行后打印的结果为BIRD EAT MI ; 如果要调用父类的 eat() 方法,只需在 Cat 的 eat() 方法中调用 super.eat() 即可

在C / C++ 中,栈空间和堆空间,栈空间的内存大小受操作系统限制,由操作系统自动来管理,速度较快,所以在函数中定义的局部变量、函数形参变量都存储在栈空间。操作系统没有限制堆空间的内存大小,只受物理内存的限制,内存需要程序员自己管理。在 C 语言中用 malloc 关键字动态分配的内存和在 C++ 中用 new 创建的对象所分配内存都存储在堆空间,内存使用完之后分别用 freedelete/delete[] 释放。

Native层发访问父类构造方法和父类实例方法

  • 头文件

    // // Created by Peng Cai on 2018/10/16. //
    
        # include
    
        # ifndef ANDROIDJNIDEMO_CALL_SUPER_H
    
        # define ANDROIDJNIDEMO_CALL_SUPER_H
    
        # ifdef __cplusplus
    
        extern "C" {
    
        # endif
    
        /*
    
        -   Class: org_professor_jni_MainActivity
        -   Method: callNativeSuperInstanceMethod
        -   Signature: ()V 
        */ 
        JNIEXPORT void JNICALL Java_org_professor_jni_MainActivity_callNativeSuperInstanceMethod (JNIEnv *, jobject);
    
        # ifdef __cplusplus
    
        }
    
        # endif
    
        # endif //ANDROIDJNIDEMO_CALL_SUPER_H
    
    
  • 实现文件

    
    // // Created by Peng Cai on 2018/10/16. //
    
    # include "include/call_super.h"
    
    # include
    
    # include
    
    # include
    
    JNIEXPORT void JNICALL Java_org_professor_jni_MainActivity_callNativeSuperInstanceMethod(JNIEnv *env, jobject instance) {
    
    
        //1. 获取类类型
        jclass birdClazz = (*env)->FindClass(env, "org/professor/jni/animal/Bird");
        if (NULL == birdClazz) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Bird Class Failed");
            return;
        }
    
        //2.获取构造函数ID 构造方法的名统一为:<init>
        jmethodID birdInstanceMethodId = (*env)->GetMethodID(env, birdClazz, "<init>",
                                                             "(Ljava/long/String;)V");
        if (NULL == birdInstanceMethodId) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Bird Instance Method Id Failed");
            return;
        }
    
        //3. 创建一个对象实例
        jstring name = (*env)->NewStringUTF(env, "YIMI");
        if (NULL == name) {
            //有可能内存不够
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Bird Name Failed");
            return;
        }
        jobject birdObj = (*env)->NewObject(env, birdClazz, birdInstanceMethodId, name);
        if (NULL == birdObj) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Bird Instance Obj Failed");
            return;
        }
    
        // 4.获取调用方法ID
        jmethodID birdGetNameMethodID = (*env)->GetMethodID(env, birdClazz, "getName",
                                                            "()Ljava/long/String;");
        if (NULL == birdGetNameMethodID) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Bird Get Name Method Id Failed");
            return;
        }
        //5. 调用自己getName方法
        jstring birdName = (*env)->CallObjectMethod(env, birdObj, birdGetNameMethodID);
        if (NULL != birdName) {
            const char *c_bird_name = (*env)->GetStringUTFChars(env, birdName,
                                                                JNI_FALSE); //转换编码格式 在C里面用 JNI_FALSE代表不拷贝到缓冲区
            if (NULL != c_bird_name) {
                __android_log_print(ANDROID_LOG_WARN, "ANIMAL bird name = %s", c_bird_name);
                //释放从java层获取到的字符串所分配的内存
                (*env)->ReleaseStringUTFChars(env, birdName, c_bird_name);
            }
        }
    
        //-------华丽分割线--------------
        //6. 获取父类的Clazz
        jclass animalClazz = (*env)->FindClass(env, "org/professor/jni/Animal");
        if (NULL == animalClazz) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Animal Class Failed");
            return;
        }
    
        //7.获取父类方法ID eat方法
        jmethodID animalEatMethodID = (*env)->GetMethodID(env, animalClazz, "eat", "()V");
        if (NULL == animalEatMethodID) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Animal Eat Method Id Failed");
            return;
        }
        //8.子类调用父类方法 eat
        //注意:birdObj是Bird的实例,animalClazz是Animal的Class引用,animalEatMethodID是Animal类中的方法ID
        (*env)->CallNonvirtualVoidMethod(env, birdObj, animalClazz, animalEatMethodID);
    
        //--------------华丽分割线------------------
        // 9.获取父类调用方法getName
        jmethodID animalGetNameMethodID = (*env)->GetMethodID(env, animalClazz, "getName",
                                                              "()Ljava/long/String;");
        if (NULL == animalGetNameMethodID) {
            __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Animal Get Name Method Id Failed");
            return;
        }
        //10.子类调用父类GetName 方法
        jstring animalName = (*env)->CallNonvirtualObjectMethod(env, birdObj, animalClazz,
                                                                animalGetNameMethodID);
        if (NULL != animalName) {
            const char *c_animal_name = (*env)->GetStringUTFChars(env, animalName, JNI_FALSE);
            if (NULL != c_animal_name) {
                __android_log_print(ANDROID_LOG_WARN, "ANIMAL animal name = %s",
                                    c_animal_name);
                //释放从java层获取到的字符串所分配的内存
                (*env)->ReleaseStringUTFChars(env, animalName, c_animal_name);
            }
        }
        quit:
        // 删除局部引用变量 jmethod 不是引用类型 (jobject或jobject的子类才属于引用变量)
        (*env)->DeleteLocalRef(env, birdClazz);
        (*env)->DeleteLocalRef(env, animalClazz);
        (*env)->DeleteLocalRef(env, name);
        (*env)->DeleteLocalRef(env, birdObj);
        (*env)->DeleteLocalRef(env, birdName);
        (*env)->DeleteLocalRef(env, animalName);
    
    } ```
    
    
    

总结

  • 调用构造方法

    jobject birdObj = (*env)->NewObject(env, birdClazz, birdInstanceMethodId, name);
    

    我们通过上面的代码去创建一个为初始化的对像并分配非存空间,然后调用对象的构造器去初始化对象。其实也可以通过下民的方式去做

    jobject birdObj = (*env)->AllocObject(env, birdClazz);
    if(NULL !=birdObj){
        (*env)->CallNonvirtualVoidMethod(env,birdObj,birdClazz,birdInstanceMethodId,name);
        if((*env)->ExceptionCheck(env)){
            goto quit; //跳转到释放函数表
        }
    }
    

    AllocObject 函数创建的是一个未初始化的对象,后面在用这个对象之前,必须调用CallNonvirtualVoidMethod, 调用对象的构造函数初始化该对象。而且在使用时一定要非常小心,确保在一个对象上面,构造函数最多被调用一次。有时,先创建一个初始化的对象,然后在合适的时间再调用构造函数的方式是很有用的。尽管如此,大部分情况下,应该使用 NewObject,尽量避免使用容易出错的 AllocObject/CallNonvirtualVoidMethod 函数。

  • 调用实例方法

    如果一个方法被定义在父类中,在子类中被覆盖,也可以调用父类中的这个实例方法。JNI 提供了一系列函数CallNonvirtualXXXMethod 来支持调用各种返回值类型的实例方法。调用一个定义在父类中的实例方法,须遵循下面的步骤:

    • 使用 GetMethodID 函数从一个指向父类的 Class 引用当中获取方法 ID。
    //7.获取父类方法ID eat方法
    jmethodID animalEatMethodID = (*env)->GetMethodID(env, animalClazz, "eat", "()V");
    if (NULL == animalEatMethodID) {
        __android_log_print(ANDROID_LOG_WARN, "ANIMAL", "Create Animal Eat Method Id Failed");
        return;
    }
    
    • 传入子类对象、父类 Class 引用、父类方法 ID 和参数,并调用 CallNonvirtualVoidMethod、 CallNonvirtualBooleanMethod、CallNonvirtualIntMethod 等一系列函数中的一个。其中CallNonvirtualVoidMethod 也可以被用来调用父类的构造函数。
    //8.子类调用父类方法 eat
    //注意:birdObj是Bird的实例,animalClazz是Animal的Class引用,animalEatMethodID是Animal类中的方法ID
    (*env)->CallNonvirtualVoidMethod(env, birdObj, animalClazz, animalEatMethodID);
    

© 著作权归作者所有

蔡小鹏
粉丝 37
博文 42
码字总数 72958
作品 0
海淀
Android工程师
私信 提问
Android JNI学习(四)——JNI的常用方法的中文API

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头
2018/05/09
0
0
如何让Java和C++接口互相调用:JNI使用指南

写在前面的话 对于cocos2d-x开发者而言,游戏对多平台的支持是一个刚需,一般而言需要支持Android和iOS。然而,在开发过程中,android 平台遇到的一系列问题着实让人头疼,本文以集成新浪微博...

zhangyujsj
2015/08/23
654
0
Android JNI开发系列(九)JNI调用Java的静态方法&实例方法

JNI调用Java的静态方法&实例方法 上面写了一个Java Bean类,里面定义了两个Native方法,分别用来调用,该类的静态方法和实例方法,实现在本地native方法里 JNI调用静态方法 注意: JVM 针对所...

蔡小鹏
2018/10/15
219
0
Android JNI开发系列(十)JNI访问 Java 实例变量和静态变量

JNI访问 Java 实例变量和静态变量 Java 中的实例变量和静态变量,在本地代码中如何来访问和修改。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过类名.变量名来...

蔡小鹏
2018/10/16
631
0
深入了解android平台的jni---本地多线程调用java代码

一、jni调用java对象 JNI提供的功能之一是在本地代码中使用Java对象。包括:创建一个java类对象和通过函数传递一个java对象。创建一个java类对象,首先需要得到得到使用FindClass/GetObject...

mfcai
2013/07/17
652
0

没有更多内容

加载失败,请刷新页面

加载更多

怎样在磁盘上查找MySQL表的大小?这里有答案

导读 我想知道 MySQL 表在磁盘上占用多少空间,但看起来很琐碎。不应该在 INFORMATION_SCHEMA.TABLES 中提供这些信息吗?没那么简单! 我想知道 MySQL 表在磁盘上占用多少空间,但看起来很琐碎...

问题终结者
39分钟前
6
0
jQuery load() 方法实现加载远程数据

jQuery load() 方法是简单但强大的 AJAX 方法。load() 方法从服务器加载数据,并把返回的数据放入被选元素中。 语法: $(selector).load(URL,data,callback);必需的 URL 参数规定您希望加载的...

前端老手
40分钟前
5
0
Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存-2

问题 上一篇Spring Boot Cache + redis 设置有效时间和自动刷新缓存,时间支持在配置文件中配置,说了一种时间方式,直接扩展注解的Value值,如: @Override@Cacheable(value = "people#${s...

xiaolyuh
48分钟前
10
0
怎样在磁盘上查找MySQL表的大小?这里有答案

我想知道 MySQL 表在磁盘上占用多少空间,但看起来很琐碎。不应该在 INFORMATION_SCHEMA.TABLES 中提供这些信息吗?没那么简单! 我想知道 MySQL 表在磁盘上占用多少空间,但看起来很琐碎。不应...

Linux就该这么学
今天
5
0
Redis

一、Redis支持的几种数据类型:字符串、List、SET、HASH、ZSET 二、Redis的缓存技术主要是为了降低关系数据库的负载并减少网站成本 三、在Redis里面,被MULTI命令和EXEC命令包围的所有命令会...

BobwithB
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部