當前位置:
首頁 > 最新 > Android深入理解JNI(一)JNI原理與靜態、動態註冊

Android深入理解JNI(一)JNI原理與靜態、動態註冊

前言

JNI不僅僅在NDK開發中應用,它更是Android系統中Java與Native交互的橋樑,不理解JNI的話,你就只能停留在Java Framework層。這一個系列我們來一起深入學習JNI。

1.JNI概述

Android系統按語言來劃分的話由兩個世界組成,分別是Java世界和Native世界。那為什麼要這麼劃分呢?Android系統由Java寫不好嗎?除了性能的之外,最主要的原因就是在Java誕生之前,就有很多程序和庫都是由Native語言寫的,因此,重複利用這些Native語言編寫的庫是十分必要的,況且Native語言編寫的庫具有更好的性能。

這樣就產生了一個問題,Java世界的代碼要怎麼使用Native世界的代碼呢,這就需要一個橋樑來將它們連接在一起,而JNI就是這個橋樑。

通過JNI,Java世界的代碼就可以訪問Native世界的代碼,同樣的,Native世界的代碼也可以訪問Java世界的代碼。

為了講解JNI我們需要分析系統的源碼,在即將出版的《Android進階之光》的最後一章中我拿MediaPlayer框架做了舉例,這裡換MediaRecorder框架來舉例,它和MediaPlayer框架的調用過程十分類似。

2.MediaRecorder框架概述

MediaRecorder我們應該都不陌生,它用於錄音和錄像。這裡不會主要介紹MediaRecorder框架,而是MediaRecorder框架中的JNI。

Java世界對應的是MediaRecorder.java,也就是我們應用開發中直接調用的類。JNI層對用的是libmedia_jni.so,它是一個JNI的動態庫。Native層對應的是libmedia.so,這個動態庫完成了實際的調用的功能。

3.Java層的MediaRecorder

我們先來查看MediaRecorder.java的源碼,截取部分和JNI有關的部分如下所示。

frameworks/base/media/java/android/media/MediaRecorder.java

在靜態代碼塊中首先調用了注釋1處的代碼,用來載入名為「media_jni「的動態庫,也就是libmedia_jni.so。接著調用注釋2處的native_init方法,注釋3處的native_init方法用native來修飾,說明它是一個native方法,表示由JNI來實現。MediaRecorder的start方法同樣也是一個native方法。

對於Java層來說只需要載入對應的JNI庫,接著聲明native方法就可以了,剩下的工作由JNI層來完成。

4.JNI層的MediaRecorder

MediaRecorder的JNI層由android_media_recorder.cpp實現,native方法native_init和start的JNI層實現如下所示。

frameworks/base/media/jni/android_media_MediaRecorder.cpp

android_media_MediaRecorder_native_init方法是native_init方法在JNI層的實現,android_media_MediaRecorder_start方法則是start方法在JNI層的實現。那麼,native_init方法是如何找到對應的android_media_MediaRecorder_native_init方法的呢?

這就需要了解JNI方法註冊的知識。

5.JNI方法註冊

JNI方法註冊分為靜態註冊和動態註冊,其中靜態註冊多用於NDK開發,而動態註冊多用於Framework開發。

靜態註冊

在AS中新建一個Java Library名為media,這裡仿照系統的MediaRecorder.java,寫一個簡單的MediaRecorder.java,如下所示。

接著進入項目的media/src/main/java目錄中執行如下命令:

第二個命令會在當前目錄中(media/src/main/java)生成com_example_MediaRecorder.h文件,如下所示。

nativeinit方法被聲明為注釋1處的方法,格式為`Java包名類名方法名`,注釋1處的方法名多了一個「l」,這是因為nativeinit方法有一個「」,它會在轉換為JNI方法時變成「_l」。

其中JNIEnv是一個指向全部JNI方法的指針,該指針只在創建它的線程有效,不能跨線程傳遞。

jclass是JNI的數據類型,對應Java的java.lang.Class實例。jobject同樣也是JNI的數據類型,對應於Java的Object。關於JNIEnv以及JNI的數據類型會在本系列的後續文章中進行介紹。

當我們在Java中調用native_init方法時,就會從JNI中尋找Java_com_example_MediaRecorder_native_1init方法,如果沒有就會報錯,如果找到就會為native_init和

Java_com_example_MediaRecorder_native_1init建立關聯,其實是保存JNI的方法指針,這樣再次調用native_init方法時就會直接使用這個方法指針就可以了。

靜態註冊就是根據方法名,將Java方法和JNI方法建立關聯,但是它有一些缺點:

JNI層的方法名稱過長。

聲明Native方法的類需要用javah生成頭文件。

初次調用JIN方法時需要建立關聯,影響效率。

我們知道,靜態註冊就是Java的Native方法通過方法指針來與JNI進行關聯的,如果Native方法知道它在JNI中對應的方法指針,就可以避免上述的缺點,這就是動態註冊。

動態註冊

JNI中有一種結構用來記錄Java的Native方法和JNI方法的關聯關係,它就是JNINativeMethod,它在jni.h中被定義:

系統的MediaRecorder採用的就是動態註冊,我們來查看它的JNI層是怎麼做的。

frameworks/base/media/jni/android_media_MediaRecorder.cpp

上面定義了一個JNINativeMethod類型的gMethods數組,裡面存儲的就是MediaRecorder的Native方法與JNI層方法的對應關係,其中注釋1處」start」是Java層的Native方法,它對應的JNI層的方法為android_media_MediaRecorder_start。」()V」是start方法的簽名信息,關於Java方法的簽名信息後續的文章會介紹。

只定義JNINativeMethod 類型的數組是沒有用的,還需要註冊它,註冊的方法為register_android_media_MediaRecorder:

frameworks/base/media/jni/android_media_MediaRecorder.cpp

register_android_media_MediaRecorder方法中return了AndroidRuntime的registerNativeMethods方法,如下所示。

frameworks/base/core/jni/AndroidRuntime.cpp

registerNativeMethods方法中又return了jniRegisterNativeMethods方法:

external/conscrypt/src/openjdk/native/JNIHelp.cpp

從注釋1處可以看出,最終調用的JNIEnv的RegisterNatives方法,JNIEnv在JNI中十分重要,後續文章會介紹它。

register_android_media_MediaRecorder方法最終會調用JNIEnv的RegisterNatives方法,但是register_android_media_MediaRecorder方法是在哪被調用的呢?答案在register_android_media_MediaRecorder方法的注釋上:JNI_OnLoad in android_media_MediaPlayer.cpp。這個JNI_OnLoad方法會在System.loadLibrary方法後調用,因為多媒體框架中的很多框架都要進行JNINativeMethod數組註冊,因此,註冊方法就被統一定義在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中,如下所示。

frameworks/base/media/jni/android_media_MediaPlayer.cpp

在JNI_OnLoad方法中調用了整個多媒體框架的註冊JNINativeMethod數組的方法,注釋1處的調用了register_android_media_MediaRecorder方法,同樣的,MediaPlayer框架的註冊JNINativeMethod數組的方法register_android_media_MediaPlayer也被調用了。

關於動態註冊就講到這裡,更多深入JNI的知識請見本系列後續的文章。

參考資料

《深入理解Android卷I》

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

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


請您繼續閱讀更多來自 劉望舒 的精彩文章:

TAG:劉望舒 |

您可能感興趣

如何理解 NVIDIA新GPU 架構 Turing的Tensor Core?
深入理解 Web Server 原理與實踐:Nginx
深入理解 ES Modules
Naturali CTO、ACL Fellow林德康:探索問答系統和機器閱讀理解
深入理解Windows頁幀號(一)
理解並實現 ResNet(Keras)
如何理解華為榮耀的CPU Turbo和GPU Turbo?
理解 OutOfMemoryError 異常
徹底理解 Node.js 中的回調(Callback)函數
MapReduce Shuffle深入理解
Windows PowerShell進階 理解Module
阿里CEO張勇:我們理解AI是Alibaba Intelligence
微軟研究院主管Eric Horvitz:我們決定讓AI理解幽默
代碼詳解:通過模擬API來理解TensorFlow
Cookie,Session和Token概念的正確理解
理解 Python 的 for 循環
何愷明CVPR演講:深入理解ResNet和視覺識別的表示學習(41 PPT)
簡單理解Vue中的nextTick
Less與TypeScript的簡單理解與應用,並使用WebPack打包靜態頁面
用汽車比喻理解OOP-Jonathan Kuhl