JNI/NDK開發指南(七)---呼叫構造方法和父類例項方法
我們先回過一下,在Java中例項化一個物件和呼叫父類例項方法的流程。先看一段程式碼:
package com.study.jnilearn;
public class Animal {
public void run() {
System.out.println("Animal.run...");
}
}
package com.study.jnilearn;
public class Cat extends Animal {
@Override
public void run() {
System.out.println(name + " Cat.run..." );
}
}
public static void main(String[] args) {
Animal cat = new Cat("湯姆");
cat.run();
}
正如你所看到的那樣,上面這段程式碼非常簡單,有兩個類Animal和Cat,Animal類中定義了run和getName兩個方法,Cat繼承自Animal,並重寫了父類的run方法。在main方法中,首先定義了一個Animal型別的變數cat,並指向了Cat類的例項物件,然後呼叫了它的run方法。在執行new Cat(“湯姆”)這段程式碼時,會先為Cat類分配記憶體空間(所分配的記憶體空間大小由Cat類的成員變數數量決定),然後呼叫Cat的帶參構造方法初始化物件。
寫過C或C++的同學應該都有一個很深刻的記憶體管理概念,棧空間和堆空間,棧空間的記憶體大小受作業系統限制,由作業系統自動來管理,速度較快,所以在函式中定義的區域性變數、函式形參變數都儲存在棧空間。作業系統沒有限制堆空間的記憶體大小,只受實體記憶體的限制,記憶體需要程式設計師自己管理。在C語言中用malloc關鍵字動態分配的記憶體和在C++中用new建立的物件所分配記憶體都儲存在堆空間,記憶體使用完之後分別用free或delete/delete[]釋放。這裡不過多的討論C/C++記憶體管理方面的知識,有興趣的同學請自行百度。
做Java的童鞋眾所周知,寫Java程式是不需要手動來管理記憶體的,記憶體管理那些煩鎖的事情全都交由一個叫GC的執行緒來管理(當一個物件沒有被其它物件所引用時,該物件就會被GC釋放)。但我覺得Java內部的記憶體管理原理和C/C++是非常相似的,上例中,Animal cat = new Cat(“湯姆”); 區域性變數cat存放在棧空間上,new Cat(“湯姆”);建立的例項物件存放在堆空間,返回一個記憶體地址的引用,儲存在cat變數中。這樣就可以通過cat變數所指向的引用訪問Cat例項當中所有可見的成員了。
所以建立一個物件分為2步:
- 為物件分配記憶體空間
- 初始化物件(呼叫物件的構造方法)
下面通過一個示例來了解在JNI中是如何呼叫物件構造方法和父類例項方法的。為了讓示例能清晰的體現構造方法和父類例項方法的呼叫流程,定義了Animal和Cat兩個類,Animal定義了一個String形參的構造方法,一個成員變數name、兩個成員函式run和getName,Cat繼承自Animal,並重寫了run方法。在JNI中實現建立Cat物件的例項,呼叫Animal類的run和getName方法。程式碼如下所示:
// Animal.java
package com.study.jnilearn;
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
System.out.println("Animal Construct call...");
}
public String getName() {
System.out.println("Animal.getName Call...");
return this.name;
}
public void run() {
System.out.println("Animal.run...");
}
}
// Cat.java
package com.study.jnilearn;
public class Cat extends Animal {
public Cat(String name) {
super(name);
System.out.println("Cat Construct call....");
}
@Override
public String getName() {
return "My name is " + this.name;
}
@Override
public void run() {
System.out.println(name + " Cat.run...");
}
}
// AccessSuperMethod.java
package com.study.jnilearn;
public class AccessSuperMethod {
public native static void callSuperInstanceMethod();
public static void main(String[] args) {
callSuperInstanceMethod();
}
static {
System.loadLibrary("AccessSuperMethod");
}
}
AccessSuperMethod類是程式的入口,其中定義了一個native方法callSuperInstanceMethod。用javah生成的jni函式原型如下:
/* Header for class com_study_jnilearn_AccessSuperMethod */
#ifndef _Included_com_study_jnilearn_AccessSuperMethod
#define _Included_com_study_jnilearn_AccessSuperMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_study_jnilearn_AccessSuperMethod
* Method: callSuperInstanceMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
實現Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod函式,如下所示:
// AccessSuperMethod.c
#include "com_study_jnilearn_AccessSuperMethod.h"
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessSuperMethod_callSuperInstanceMethod
(JNIEnv *env, jclass cls)
{
jclass cls_cat;
jclass cls_animal;
jmethodID mid_cat_init;
jmethodID mid_run;
jmethodID mid_getName;
jstring c_str_name;
jobject obj_cat;
const char *name = NULL;
// 1、獲取Cat類的class引用
cls_cat = (*env)->FindClass(env, "com/study/jnilearn/Cat");
if (cls_cat == NULL) {
return;
}
// 2、獲取Cat的構造方法ID(構造方法的名統一為:<init>)
mid_cat_init = (*env)->GetMethodID(env, cls_cat, "<init>", "(Ljava/lang/String;)V");
if (mid_cat_init == NULL) {
return; // 沒有找到只有一個引數為String的構造方法
}
// 3、建立一個String物件,作為構造方法的引數
c_str_name = (*env)->NewStringUTF(env, "湯姆貓");
if (c_str_name == NULL) {
return; // 建立字串失敗(記憶體不夠)
}
// 4、建立Cat物件的例項(呼叫物件的構造方法並初始化物件)
obj_cat = (*env)->NewObject(env,cls_cat, mid_cat_init,c_str_name);
if (obj_cat == NULL) {
return;
}
//-------------- 5、呼叫Cat父類Animal的run和getName方法 --------------
cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal");
if (cls_animal == NULL) {
return;
}
// 例1: 呼叫父類的run方法
mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V"); // 獲取父類Animal中run方法的id
if (mid_run == NULL) {
return;
}
// 注意:obj_cat是Cat的例項,cls_animal是Animal的Class引用,mid_run是Animal類中的方法ID
(*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);
// 例2:呼叫父類的getName方法
// 獲取父類Animal中getName方法的id
mid_getName = (*env)->GetMethodID(env, cls_animal, "getName", "()Ljava/lang/String;");
if (mid_getName == NULL) {
return;
}
c_str_name = (*env)->CallNonvirtualObjectMethod(env, obj_cat, cls_animal, mid_getName);
name = (*env)->GetStringUTFChars(env, c_str_name, NULL);
printf("In C: Animal Name is %s\n", name);
// 釋放從java層獲取到的字串所分配的記憶體
(*env)->ReleaseStringUTFChars(env, c_str_name, name);
quit:
// 刪除區域性引用(jobject或jobject的子類才屬於引用變數),允許VM釋放被區域性變數所引用的資源
(*env)->DeleteLocalRef(env, cls_cat);
(*env)->DeleteLocalRef(env, cls_animal);
(*env)->DeleteLocalRef(env, c_str_name);
(*env)->DeleteLocalRef(env, obj_cat);
}
執行結果:
程式碼講解 - 呼叫構造方法
呼叫構造方法和呼叫物件的例項方法方式是相似的,傳入”< init >”作為方法名查詢類的構造方法ID,然後呼叫JNI函式NewObject呼叫物件的建構函式初始化物件。如下程式碼所示:
obj_cat = (*env)->NewObject(env,cls_cat,mid_cat_init,c_str_name);
上述這段程式碼呼叫了JNI函式NewObject建立了Class引用的一個例項物件。這個函式做了2件事情,1> 建立一個未初始化的物件並分配記憶體空間 2> 呼叫物件的建構函式初始化物件。這兩步也可以分開進行,為物件分配記憶體,然後再初始化物件,如下程式碼所示:
// 1、建立一個未初始化的物件,並分配記憶體
obj_cat = (*env)->AllocObject(env, cls_cat);
if (obj_cat) {
// 2、呼叫物件的建構函式初始化物件
(*env)->CallNonvirtualVoidMethod(env,obj_cat, cls_cat, mid_cat_init, c_str_name);
if ((*env)->ExceptionCheck(env)) { // 檢查異常
goto quit;
}
}
AllocObject函式建立的是一個未初始化的物件,後面在用這個物件之前,必須呼叫CallNonvirtualVoidMethod呼叫物件的建構函式初始化該物件。而且在使用時一定要非常小心,確保在一個物件上面,建構函式最多被呼叫一次。有時,先建立一個初始化的物件,然後在合適的時間再呼叫建構函式的方式是很有用的。儘管如此,大部分情況下,應該使用 NewObject,儘量避免使用容易出錯的AllocObject/CallNonvirtualVoidMethod函式。
程式碼講解 - 呼叫父類例項方法
如果一個方法被定義在父類中,在子類中被覆蓋,也可以呼叫父類中的這個例項方法。JNI 提供了一系列函式CallNonvirtualXXXMethod來支援呼叫各種返回值型別的例項方法。呼叫一個定義在父類中的例項方法,須遵循下面的步驟:
1.使用GetMethodID函式從一個指向父類的Class引用當中獲取方法ID
cls_animal = (*env)->FindClass(env, "com/study/jnilearn/Animal");
if (cls_animal == NULL) {
return;
}
//例1: 呼叫父類的run方法
mid_run = (*env)->GetMethodID(env, cls_animal, "run", "()V"); // 獲取父類Animal中run方法的id
if (mid_run == NULL) {
return;
}
2.傳入子類物件、父類Class引用、父類方法 ID 和引數,並呼叫 CallNonvirtualVoidMethod、
CallNonvirtualBooleanMethod、CallNonvirtualIntMethod等一系列函式中的一個。其中CallNonvirtualVoidMethod 也可以被用來呼叫父類的建構函式。
// 注意:obj_cat是Cat的例項,cls_animal是Animal的Class引用,mid_run是Animal類中的方法ID
(*env)->CallNonvirtualVoidMethod(env, obj_cat, cls_animal, mid_run);
其實在開發當中,這種呼叫父類例項方法的情況是很少遇到的,通常在 JAVA 中可以很簡單地做到: super.func();但有些特殊需求也可能會用到,所以知道有這麼回事還是很有必要的。
相關推薦
JNI/NDK開發指南(七)---呼叫構造方法和父類例項方法
我們先回過一下,在Java中例項化一個物件和呼叫父類例項方法的流程。先看一段程式碼: package com.study.jnilearn; public class Animal { public void run() { System.out.p
JNI/NDK開發指南(九)——JNI呼叫效能測試及優化
在前面幾章我們學習到了,在Java中宣告一個native方法,然後生成本地介面的函式原型宣告,再用C/C++實現這些函式,並生成對應平臺的動態共享庫放到Java程式的類路徑下,最後在Java程式中呼叫宣告的native方法就間接的呼叫到了C/C++編寫的函數
JNI/NDK開發指南(八)---JNI呼叫效能測試及優化
在前面幾章我們學習到了,在Java中宣告一個native方法,然後生成本地介面的函式原型宣告,再用C/C++實現這些函式,並生成對應平臺的動態共享庫放到Java程式的類路徑下,最後在Java程式中呼叫宣告的native方法就間接的呼叫到了C/C++編寫的函數了,在C/C++
JNI/NDK開發指南(三)——JNI數據類型及與Java數據類型的映射關系
ons 轉換 類型 art return http 異常 array src 轉載請註明出處:http://blog.csdn.net/xyang81/article/details/42047899 當我們在調用一個
JNI/NDK開發指南(十)——JNI區域性引用、全域性引用和弱全域性引用
這篇文章比較偏理論,詳細介紹了在編寫原生代碼時三種引用的使用場景和注意事項。可能看起來有點枯燥,但引用是在JNI中最容易出錯的一個點,如果使用不當,容易使程式造成記憶體溢位,程式崩潰等現象。所以講得比較細,有些地方看起來可能比較囉嗦,還請輕啪!《An
JNI/NDK開發指南(一)—— JNI開發流程及HelloWorld
JNI全稱是Java Native Interface(Java本地介面)單詞首字母的縮寫,本地介面就是指用C和C++開發的介面。由於JNI是JVM規範中的一部份,因此可以將我們寫的JNI程式在任何實現了JNI規範的Java虛擬機器中執行。同時,這個特性使我們可
JNI/NDK開發指南(三)——JNI資料型別及與Java資料型別的對映關係
當我們在呼叫一個Java native方法的時候,方法中的引數是如何傳遞給C/C++本地函式中的呢?Java方法中的引數與C/C++函式中的引數,它們之間是怎麼轉換的呢?我猜你應該
JNI/NDK開發指南(六)——C/C++訪問Java例項方法和靜態方法
通過前面5章的學習,我們知道了如何通過JNI函式來訪問JVM中的基本資料型別、字串和陣列這些資料型別。下一步我們來學習原生代碼如何與JVM中任意物件的屬性和方法進行互動。比如原生代碼呼叫Java層某個物件的方法或屬性,也就是通常我們所說的來自C/C++層本地函
JNI/NDK開發指南(開山篇)
相信很多做過Java或Android開發的朋友經常會接觸到JNI方面的技術,由其做過Android的朋友,為了應用的安全性,會將一些複雜的邏輯和演算法通過原生代碼(C或C++)來實現,然後
小橙書閱讀指南(七)——優先隊列和索引優先隊列
最小優先隊列 ner 個數 str 位置 targe nds -- alc 算法描述:許多應用程序都需要按照順序處理任務,但是不一定要求他們全部有序,或是不一定要一次就將他們排序。很多情況下我們只需要處理當前最緊急或擁有最高優先級的任務就可以了。面對這樣的需求,優先隊列算法
WEB開發者之混合開發APP(七), 預載入和自定義事件
混合開發App同h5頁面開發,完全不同之一就是預載入技術。因為不可能所有的頁面開啟時,都需要重新建立, (1) 每次重新建立頁面,耗費效能; (2)新建頁面時,如果載入耗時較長,則會出現類似白屏問題,體驗極差。 預載入和自定義事件,就可以解
java基礎學習總結(七):Cloneable介面和Object的clone()方法
為什麼要克隆 為什麼要使用克隆,這其實反映的是一個很現實的問題,假如我們有一個物件: public class SimpleObject implements Cloneable { private String str; public SimpleObject()
JNI/NDK開發指南(2)
清除 onu 呼叫 rac 個人理解 運行 ati clas 函數 1.生成動態庫.so,存放於手機的system/lib/中(APP怎樣將.so存入該文件夾,奇怪?????),Java層調用JNI的類會運行靜態代碼System.loadLibrary("***")將手
JQuery.Gantt開發指南(轉)
導航 資源文件 實用工具 ati img nmon 開發 反序 public 說明 日前需要用到甘特圖,以下轉載內容源自網絡。 ? 概述 1.JQuery.Gantt是一個開源的基於JQuery庫的用於實現甘特圖效果的可擴展功能的JS組件庫。 ?前端頁面 o 資源引用
Qt與FFmpeg聯合開發指南(三)——編碼(1):代碼流程演示
開啟 fault 原因 上下 sizeof ffmpeg 不同步 目前 直接 前兩講演示了基本的解碼流程和簡單功能封裝,今天我們開始學習編碼。編碼就是封裝音視頻流的過程,在整個編碼教程中,我會首先在一個函數中演示完成的編碼流程,再解釋其中存在的問題。下一講我們會將編碼功能進
Qt與FFmpeg聯合開發指南(四)——編碼(2):完善功能和基礎封裝
v_op buffer 目前 front from 幀率 inter 博客 int 上一章我用一個demo函數演示了基於Qt的音視頻采集到編碼的完整流程,最後經過測試我們也發現了代碼中存在的問題。本章我們就先處理幾個遺留問題,再對代碼進行完善,最後把編碼功能做基礎封裝。 一
Git工程開發實踐(七)——GitLab服務搭建
gin ash -c ons smtp服務 ota shell roo 目前 Git工程開發實踐(七)——GitLab服務搭建 操作系統:RHEL 7.3 WorkStation 一、GitLab簡介 1、GitLab簡介 ?GitLab是一個利用Ruby on Rails
LayIM.AspNetCore Middleware 開發日記(七)Asp.Net.Core.SignalR閃亮登場
you socket https image upm ogg 通訊 () aspnet 前言 ??前幾篇介紹了整個中間件的構成,路由,基本配置等等.基本上沒有涉及到通訊部分。不過已經實現了融雲的通訊功能,由於是第三方的就不在單獨去寫。正好.NET Core SignalR已
Android 開發:(七)常用佈局屬性詳解
第一類:屬性值為true或false android:layout_centerHrizontal 水平居中 android:layout_centerVertical 垂直居中 android:layout_centerInparent 相對於父元素完全居中 androi
使用SpringBoot2.0搭建企業級應用開發框架(七)整合Shiro
準備 首先建立使用者許可權表 //使用者表 CREATE TABLE `sys_user` ( `id` varchar(32) NOT NULL COMMENT 'id', `username` varchar(64) DEFAULT NULL COMMENT '