當前位置:
首頁 > 最新 > Android框架層基礎JNI

Android框架層基礎JNI

今日資訊

這兩天抄的最火的那就是:程序員,成為北京人,只差月薪7w了。根據北京市統計局、市人力社保局發布數據,2016 年度北京市職工年平均工資為 92477 元,月平均工資為 7706 元。而近日某招聘網站新鮮出爐的《2018 旺季人才趨勢報告》中顯示,北京市平均月薪達到 10712 元。由此估算出月薪至少要 7 萬可申請辦理人才引進。所以,北京的程序員,月薪應稅收入達到 7w 即可直接落戶,成為北京人啦!程序員別灰心,我們已經成功一半了,就差月薪7w了。。。

正文


JNI在Android系統中所處的位置如圖所示

圖片來源於51CTO.com

注意JNI與NDK的區別:NDK是為了便於開發基於JNI的應用而提供的一套開發和編譯工具集,而JNI則是一套編程介面,可以運用在應用層,也可以運用在應用框架層,以實現Java與本地代碼的互操作。

JNI編程模型的結構可以概括為以下三個步驟:

* Java層聲明native方法

* JNI層實現Java層聲明的native方法,在JNI層可以調用底層庫或者回調Java層方法。這部分將被編譯為動態庫(so文件)供系統載入

* 載入JNI層代碼編譯後生成的共享庫


2.1 Log系統Java層分析

Log系統Java層中Log.java文件中定義了兩個native方法

2.2 Log系統的JNI層

打開android_util_Log.cpp文件

JNI層的實現方法只是根據一定的規則與Java層聲明的方法做了一個映射,然後通過調用本地塊函數或者JNIEnv提供的JNI函數響應Java層的調用

2.3 Log系統的JNI方法註冊

定位到android_util_Log.cpp的源碼

GMethods用來存儲JNINativeMethod類型的數據

JNINativeMethod是一個結構體類型,保存了Java層聲明函數和JNI實現函數的一一對應的關係。

可是如何告訴虛擬機這種對應的關係?我們繼續看android_util_Log.cpp中的register_android_util_Log函數

這裡我們來解析兩個問題

* registerMethods函數的作用是什麼?

定位到函數內部

在JNIHelp.h和JNIHelp.cpp中找到函數jniRegisterNativeMethods的實現

這裡最終調用了JNIEnv的RegisterNatives函數,將gMethods中存儲的方法關聯信息傳遞給虛擬機。其作用就是向clazz參數指定的類註冊本地方法,這樣虛擬機就得到了Java層和JNI層之間的對應關係,就可以實現Java和C/C++代碼的互操作了

* register_android_util_Log函數是在哪調用的

這個函數是在系統啟動過程中通過AndroidRuntime.cpp的register_jni_procs方法執行的,進而調用到register_android_util_Log函數將這種映射關係註冊給Dalvik虛擬機

建立Java層聲明函數和JNI層實現函數之間的對應關係有兩種方式

* 遵守JNI方法命名規範

* 採用函數註冊方式

應用層多採用第一種,框架層多採用第二種。


我們先看看JNIEnv的體系結構

從圖中我們可以看出JNIEnv首先指向一個線程相關的結構,該結構又指向一個指針數組,在這個指針數組中每一個元素最終又指向一個JNI函數,所有可以通過JNIEnv去調用JNI函數。JNIEnv是一個線程相關的結構體, 該結構體代表了 Java 在本線程的運行環境

JavaVM和JNIEnv的關係

* JavaVM是Java虛擬機在JNI層的代表,JNI全局只有一個

* JNIEnv是一個線程相關的結構體,每個線程都有一個,JNI中可能有多個

相關源碼就不看了,我們直接給出結論

* 在C++中:JNIEnv就是struct _JNIEnv,JNIEnv * env等價於struct _JNIEnv * env,在調用JNI函數的時候,只需要env->FindClass就會直接調用JNINativeInterface結構體里定義的函數指針,而無需首先對env解引用

* 在C中:JNIEnv就是const struct JNINativeInterface * ,JNIEnv * env實際上等價於const struct JNINativeInterface* env,因此要得到JNINativeInterface結構體內的函數指針就必須先對env解引用得到(env),即const struct JNINativeInterface,這個指針才是真正指向JNINativeInterface結構體的指針,然後再通過它調用具體的JNI函數,因此需要(env)->FindClass這樣調用

注意:

JNIEnv只在當前線程中有效。本地方法不能講JNIEnv從一個線程傳遞到另一個線程,相同的Java線程對本地方法多次調用時傳遞的JNIEnv是相同的。但是一個本地方法可以被不同的Java線程調用,因此可以接受不同的JNIEnv


4.1 Java數據類型和JNI數據類型轉換

基本數據類型轉換

引用數據類型轉換

4.2 JNI方法命名規則

JNI方法由以下幾個部分組成:

* 前綴: Java_

* 類的全限定名,用下劃線進行分隔(_):com_lms_jni_JniTest

* 方法名:getTestString

* jni函數指定第一個參數: JNIEnv *

* jni函數指定第二個參數: jobject

* 實際Java參數: jstring, jint ….

* 返回值的參數 : jstring, jint….

如果採用函數註冊的方式,可以不遵守上述規則

4.3 JNI方法簽名規則

JNI的簽名規則如下:

(參數1類型簽名參數2類型簽名···參數n類型簽名)返回值類型簽名

注意:中間沒有任何符號

注意:類的簽名規則是:」L+全限定類名+;」,其中全限定類名由」/」分隔。例如

其方法簽名為:(ILjava/lang/String;[I)J

5.1 訪問Java對象

JNI提供的類和對象操作的方法有很多,常用的有兩個:FindClass和GetObjectClass,在C和C++中有不同的函數原型:

* C++:

* C:

我們看看Log系統是如何操作Java對象的

5.2 操作成員變數和方法

我們還是來看看Log系統是如何操作成員變數的

首先通過FindClass找到android/util/Log的類信息clazz,然後以clazz為參數調用GetStaticFieldId(clazz, 「DEBUG」, 「I」),其函數原型如下:

jfieldId GetStaticFieldId(jclass clazz, const char * name, const char * sig);

最後將返回的jfieldId傳遞給GetStaticIntField方法得到android/util/Log.java的成員變數DEBUG的值

JNI操Java層的方法類似,流程是 :

FindClass->GetMethodId->返回jMethodId->CallMethod

5.3 全局引用、弱全局引用、局部引用

局部引用:可以增加引用計數,作用範圍為本線程,生命周期為一次Native調用。局部引用包括多數JNI函數創建的引用,Native方法返回值和參數。局部引用只在創建它的Native方法的線程中有效,並且只在Native方法的一次調用中有效,在該方法返回後,被虛擬機回收(不同於C中的局部變數,返回後會立即回收)。

全局引用:可以增加引用計數。作用範圍為多線程,多個Native方法,生命周期到顯式釋放。全局引用通過JNI函數NewGlobalRef創建,並通過DeleteGlobalRef釋放。如果程序員不顯式釋放,將永遠不會被垃圾回收。

弱全局引用:不能增加引用計數。作用範圍為多線程,多個Native方法,生命周期到顯式釋放。不過其對應的Java對象生命周期依然取決於虛擬機,意思是即便弱全局引用沒有被釋放,其引用的Java對象可能已經被釋放。弱全局引用通過JNI函數NewWeakGlobalRef創建,並通過DeleteWeakGlobalRef釋放。弱全局引用的優點是:既可以保存對象,又不會阻止該對象被回收。

註:本文參考《Android設計與實現卷I(楊雲君)》

記得關注wu小鵬哦~

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 wu小鵬 的精彩文章:

TAG:wu小鵬 |