Java与下层的沟通,UI由java来写,底层的硬件层由C/C++来写。 上下层的沟通需要一个interface,就是说有一个构架,我们的C++需要提供一个怎样的接口,Java也必须知道我需要怎么调用c/C++,这些都是固定规定的。这里有一个框架,在Android中,这里提供的是JIN。 这样我们的UI可以不用改变,我们的的层的硬件或者驱动改变的时候,只要改变底层的C++的lib,也就是.so文件。这样封装,把软硬分开,把UI和实现分开。 JNI是一个很强大的框架,现在google希望通过它把所有的东东都容纳进来,注意Android只是一个框架,而不是一个OS。所以Linux具有多强的可移植性,我们的Android就由多大的可移植性,另外,由于Java强大的平台无关性,两个相结合,就会是整个操作系统变得很强大。下面介绍高焕堂的PPT。 23:JNI與Android VM之關係 ---- 本文摘錄自 高煥堂老師 的Android課程講議 1. 從 如何載入*.so檔案談起 由於 Android 的應用層級類別都是以 Java 撰寫的,這些 Java 類別轉譯為 Dex 型式的 Bytecode 之後,必須仰賴 Dalvik 虛擬機器 (VM: Virtual Machine) 23:JNI與Android VM之關係 ---- 本文摘錄自 高煥堂老師 的Android課程講議 1. 從如何載入*.so檔案談起 由於Android的應用層級類別都是以Java撰寫的,這些Java類別轉譯為Dex型式的Bytecode之後,必須仰賴Dalvik虛擬機器(VM: Virtual Machine)來執行之。VM在Android平台裡,扮演很重要的角色。 此外,在執行Java類別的過程中,如果Java類別需要與C組件溝通時,VM就會去載入C組件,然後讓Java的函數順利地呼叫到C組件的函數。此時,VM扮演著橋樑的角色,讓Java與C組件能透過標準的JNI介面而相互溝通。 應用層級的Java類別是在虛擬機器(VM: Vitual Machine)上執行的,而C組件不是在VM上執行,那麼Java程式又如何要求VM去載入(Load)所指定的C組件呢? 可使用下述指令: System.loadLibrary(*.so的檔名); 例如,在上一節的範例裡的NativeJniAdder類別,其程式碼: 看看 Android框架裡所提供的MediaPlayer.java類別,內含指令: public class MediaPlayer{ static { System.loadLibrary("media_jni"); } …….. } 這要求VM去載入Android的/system/lib/libmedia_jni.so檔案。載入*.so檔之後,Java類別與*.so檔就匯合起來,一起執行了。 2. 如何撰寫*.so的入口函數 ---- JNI_OnLoad()與JNI_OnUnload()函數之用途 當VM執行到System.loadLibrary()函數時,首先會去執行C組件裡的JNI_OnLoad()函數。它的用途有二: 1. 告訴VM此C組件使用那一個JNI版本。如果你的*.so檔沒有提供JNI_OnLoad()函數,VM會默認該*.so檔是使用最老的JNI 1.1版本。由於新版的JNI做了許多擴充,如果需要使用JNI的新版功能,例如JNI 1.4的 java.nio.ByteBuffer, 就必須藉由JNI_OnLoad()函數來告知VM。 2. 由於VM執行到System.loadLibrary()函數時,就會立即先呼叫JNI_OnLoad(),所以C組件的開發者可以藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization)。 例如,在Android的/system/lib/libmedia_jni.so檔案裡,就提供了JNI_OnLoad()函數,其程式碼片段為: //#define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" ……… jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed/n"); goto bail; } assert(env != NULL); if (register_android_media_MediaPlayer(env) < 0) { LOGE("ERROR: MediaPlayer native registration failed/n"); goto bail; } if (register_android_media_MediaRecorder(env) < 0) { LOGE("ERROR: MediaRecorder native registration failed/n"); goto bail; } if (register_android_media_MediaScanner(env) < 0) { LOGE("ERROR: MediaScanner native registration failed/n"); goto bail; } if (register_android_media_MediaMetadataRetriever(env) < 0) { LOGE("ERROR: MediaMetadataRetriever native registration failed/n"); goto bail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; } // KTHXBYE 此函數回傳JNI_VERSION_1_4值給VM,於是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的動作(可呼叫任何本地函數),例如指令: if (register_android_media_MediaPlayer(env) < 0) { LOGE("ERROR: MediaPlayer native registration failed/n"); goto bail; } 就將此組件提供的各個本地函數(Native Function)登記到VM裡,以便能加快後續呼叫本地函數之效率。 JNI_OnUnload()函數與JNI_OnLoad()相對應的。在載入C組件時會立即呼叫JNI_OnLoad()來進行組件內的初期動作;而當VM釋放該C組件時,則會呼叫JNI_OnUnload()函數來進行善後清除動作。當VM呼叫JNI_OnLoad()或JNI_Unload()函數時,都會將VM的指標(Pointer)傳遞給它們,其參數如下: jint JNI_OnLoad(JavaVM* vm, void* reserved) { ……… } jint JNI_OnUnload(JavaVM* vm, void* reserved){ ……… } 在JNI_OnLoad()函數裡,就透過VM之指標而取得JNIEnv之指標值,並存入env指標變數裡,如下述指令: jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed/n"); goto bail; } } 由於VM通常是多執行緒(Multi-threading)的執行環境。每一個執行緒在呼叫JNI_OnLoad()時,所傳遞進來的JNIEnv指標值都是不同的。為了配合這種多執行緒的環境,C組件開發者在撰寫本地函數時,可藉由JNIEnv指標值之不同而避免執行緒的資料衝突問題,才能確保所寫的本地函數能安全地在Android的多執行緒VM 裡安全地執行。基於這個理由,當在呼叫C組件的函數時,都會將JNIEnv指標值傳遞給它,如下: jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; ………. if (register_android_media_MediaPlayer(env) < 0) { ………. } } 這JNI_OnLoad()呼叫register_android_media_MediaPlayer(env)函數時,就將env指標值傳遞過去。如此,在register_android_media_MediaPlayer()函數就能藉由該指標值而區別不同的執行緒,以便化解資料衝突的問題。 例如,在register_android_media_MediaPlayer()函數裡,可撰寫下述指令: if ((*env)->MonitorEnter(env, obj) != JNI_OK) { ………. } 查看是否已經有其他執行緒進入此物件,如果沒有,此執行緒就進入該物件裡執行了。還有,也可撰寫下述指令: if ((*env)->MonitorExit(env, obj) != JNI_OK) { ……… } 查看是否此執行緒正在此物件內執行,如果是,此執行緒就會立即離開。 3. registerNativeMethods()函數之用途 應用層級的Java類別透過VM而呼叫到本地函數。一般是仰賴VM去尋找*.so裡的本地函數。如果需要連續呼叫很多次,每次都需要尋找一遍,會多花許多時間。此時,組件開發者可以自行將本地函數向VM進行登記。例如,在Android的/system/lib/libmedia_jni.so檔案裡的程式碼片段如下: //#define LOG_NDEBUG 0 #define LOG_TAG "MediaPlayer-JNI" ……… static JNINativeMethod gMethods[] = { {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource}, {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, {"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, {"_start", "()V", (void *)android_media_MediaPlayer_start}, {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo}, {"_pause", "()V", (void *)android_media_MediaPlayer_pause}, {"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying}, {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition}, {"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration}, {"_release", "()V", (void *)android_media_MediaPlayer_release}, {"_reset", "()V", (void *)android_media_MediaPlayer_reset}, {"setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType}, {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping}, {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume}, {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt}, {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize}, }; ……… static int register_android_media_MediaPlayer(JNIEnv *env){ ……… return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } ………. // jint JNI_OnLoad(JavaVM* vm, void* reserved){ ……… if (register_android_media_MediaPlayer(env) < 0) { LOGE("ERROR: MediaPlayer native registration failed/n"); goto bail; } ………. } 當VM載入libmedia_jni.so檔案時,就呼叫JNI_OnLoad()函數。接著,JNI_OnLoad()呼叫register_android_media_MediaPlayer()函數。此時,就呼叫到AndroidRuntime::registerNativeMethods()函數,向VM(即AndroidRuntime)登記gMethods[]表格所含的本地函數了。簡而言之,registerNativeMethods()函數的用途有二: 1. 更有效率去找到函數。 2. 可在執行期間進行抽換。由於gMethods[]是一個<名稱,函數指標>對照表,在程式執行時,可多次呼叫registerNativeMethods()函數來更換本地函數之指標,而達到彈性抽換本地函數之目的。 ---- END ----

更多相关文章

  1. 【转】UML建模與Android應用程式開發(上)
  2. No 93 · android xml的生成和解析
  3. adb top 指令,查看系统进程
  4. Android(安卓)程式开发:(廿一)消息传递 —— 21.3 使用Intent发送短
  5. Jollen 的 Android(安卓)系統管理雜記, #1: 關於 android.uid.sy
  6. Android开发从入门到精通(7)_1
  7. adb 指令
  8. To fetch EDID from android device
  9. c/c++ android 平台交叉编译 {ERROR: Failed to create toolchai

随机推荐

  1. 使用代码为textview设置drawableLeft
  2. Android(安卓)- DownloadManager的使用
  3. Android颜色对照表
  4. [置顶] Android(安卓)2.3.5源代码 更新至
  5. 2010.11.28(3)———android AlertDialog
  6. 第3.2.1节 android基本视图
  7. (Android) Download Images by AsyncTask
  8. Android——ImageButton【图片按钮】的点
  9. Android(安卓)Module中导入aar
  10. Android第五期 - 更新自己的apk本地与网