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