Android JNI開發系列(十一) JNI 訪問父類的構造方法和父類實例方法 原 薦

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);
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章