android jni native 與java 交互

最近在看android jni,網上搜索了些資料,這裏彙總下:

*******

經驗分享 JNI程序 java.lang.UnsatisfiedLinkError 的一種原因可能性

正常的操作順序是 
1 編輯c文件
2 make生成so文件
3 編輯java文件
OK

我一開始的做法是,先在java中添加了native方法,(這時候Eclipse給自動編譯了)
然後才編輯c,make,結果導致新版本的so文件沒有編譯進去


*****

導入NDK下的helloJNI ,運行出錯

這個時候直接運行的話,程序肯定會崩潰的。因爲動態鏈接庫還沒編譯好。Ndk根目錄下的ndk-build負責編譯so文件。首先進入項目所在目錄,然後運行ndk-build即可。


****轉自: http://mysuperbaby.iteye.com/blog/915425

個人認爲下面這篇轉載的文章寫的很清晰很不錯. 注意Android平臺上的JNI機制使用包括Java代碼中調用Native模塊以及Native代碼中調用Java模塊.

 

http://www.ophonesdn.com/article/show/263

 

衆所周知,OPhone平臺上的應用開發主要基於Java語言,但平臺完全支持且提供了一定的Native開發能力(主要是C/C++),使得開發者可以藉助JNI更深入的實現創意。本文主要介紹OPhone平臺的JNI機制和Native模塊開發與發佈的方法。

JNI簡介
Java Native Interface(JNI)是Java提供的一個很重要的特性。它使得用諸如C/C++等語言編寫的代碼可以與運行於Java虛擬機(JVM)中的 Java代碼集成。有些時候,Java並不能滿足你的全部開發需求,比如你希望提高某些關鍵模塊的效率,或者你必須使用某個以C/C++等Native語 言編寫的程序庫;此時,JNI就能滿足你在Java代碼中訪問這些Native模塊的需求。JNI的出現使得開發者既可以利用Java語言跨平臺、類庫豐 富、開發便捷等特點,又可以利用Native語言的高效。
 
 
圖1 JNI與JVM的關係
 
 
實際上,JNI是JVM實現中的一部分,因此Native語言和Java代碼都運行在JVM的宿主環境(Host Environment),正如圖1所示。此外,JNI是一個雙向的接口:開發者不僅可以通過JNI在Java代碼中訪問Native模塊,還可以在 Native代碼中嵌入一個JVM,並通過JNI訪問運行於其中的Java模塊。可見,JNI擔任了一個橋樑的角色,它將JVM與Native模塊聯繫起 來,從而實現了Java代碼與Native代碼的互訪。在OPhone上使用Java虛擬機是爲嵌入式設備特別優化的Dalvik虛擬機。每啓動一個應 用,系統會建立一個新的進程運行一個Dalvik虛擬機,因此各應用實際上是運行在各自的VM中的。Dalvik VM對JNI的規範支持的較全面,對於從JDK 1.2到JDK 1.6補充的增強功能也基本都能支持。
開發者在使用JNI之前需要充分了解其優缺點,以便合理選擇技術方案實現目標。JNI的優點前面已經講過,這裏不再重複,其缺點也 是顯而易見的:由於Native模塊的使用,Java代碼會喪失其原有的跨平臺性和類型安全等特性。此外,在JNI應用中,Java代碼與Native代 碼運行於同一個進程空間內;對於跨進程甚至跨宿主環境的Java與Native間通信的需求,可以考慮採用socket、Web Service等IPC通信機制來實現。
 
在OPhone開發中使用JNI
正如我們在上一節所述,JNI是一個雙向的接口,所以交互的類型可以分爲在Java代碼中調用Native模塊和在Native代碼中調用Java模塊兩種。下面,我們就使用一個Hello-JNI的示例來分別對這兩種交互方式的開發要點加以說明。
 
Java調用Native模塊
Hello-JNI這個示例的結構很簡單:首先我們使用Eclipse新建一個OPhone應用的Java工程,並添加一個 com.example.hellojni.HelloJni的類。這個類實際上是一個Activity,稍後我們會創建一個TextView,並顯示一 些文字在上面。
要在Java代碼中使用Native模塊,必須先對Native函數進行聲明。在我們的例子中,打開HelloJni.java文件,可以看到如下的聲明:
 
生成。h文件
 javah -classpath ../bin/classes -d ../jni com.example.TestJNI.Nadd

  1. /* A native method that is implemented by the  
  2.    * 'hello-jni' native library, which is packaged  
  3.    * with this application.  
  4.    */   
  5.   public   native  String  stringFromJNI();  
Java代碼  收藏代碼
  1. /* A native method that is implemented by the 
  2.    * 'hello-jni' native library, which is packaged 
  3.    * with this application. 
  4.    */  
  5.   public native String  stringFromJNI();  
  
從上述聲明中我們可以知道,這個stringFromJNI()函數就是要在Java代碼中調用的Native函數。接下來我們要創建一個hello-jni.c的C文件,內容很簡單,只有如下一個函數:
 
 
  1. #include <string.h>  
  2. #include <jni.h>  
  3. jstring  
  4. Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  
  5.                                                  jobject thiz ) {  
  6.         return  (*env)->NewStringUTF(env,  "Hello from JNI !" );  
  7. }  
Java代碼  收藏代碼
  1. #include <string.h>  
  2. #include <jni.h>  
  3. jstring  
  4. Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  
  5.                                                  jobject thiz ) {  
  6.         return (*env)->NewStringUTF(env, "Hello from JNI !");  
  7. }  

 

從函數名可以看出,這個Native函數對應的正是我們在com.example.hellojni.HelloJni這個中聲明的Native函數String stringFromJNI()的具體實現。
 
從上面Native函數的命名上我們可以瞭解到JNI函數的命名規則: Java代碼中的函數聲明需要添加native 關鍵 字;Native的對應函數名要以“Java_”開頭,後面依次跟上Java的“package名”、“class名”、“函數名”,中間以下劃線“_” 分割,在package名中的“.”也要改爲“_”。此外,關於函數的參數和返回值也有相應的規則。對於Java中的基本類型如int 、double 、char 等,在Native端都有相對應的類型來表示,如jint 、jdouble 、jchar 等;其他的對象類型則統統由jobject 來表示(String 是個例外,由於其使用廣泛,故在Native代碼中有jstring 這個類型來表示,正如在上例中返回值String 對應到Native代碼中的返回值jstring )。而對於Java中的數組,在Native中由jarray 對應,具體到基本類型和一般對象類型的數組則有jintArray 等和jobjectArray 分別對應(String 數組在這裏沒有例外,同樣用jobjectArray 表示)。還有一點需要注意的是,在JNI的Native函數中,其前兩個參數JNIEnv *和jobject是必需的——前者是一個JNIEnv 結構體的指針,這個結構體中定義了很多JNI的接口函數指針,使開發者可以使用JNI所定義的接口功能;後者指代的是調用這個JNI函數的Java對象,有點類似於C++中的this 指針。在上述兩個參數之後,還需要根據Java端的函數聲明依次對應添加參數。在上例中,Java中聲明的JNI函數沒有參數,則Native的對應函數只有類型爲JNIEnv *和jobject 的兩個參數。
 
當然,要使用JNI函數,還需要先加載Native代碼編譯出來的動態庫文件(在Windows上是.dll,在Linux上則爲.so)。這個動作是通過如下語句完成的:
 
  1. static  {  
  2.     System.loadLibrary("hello-jni" );  
  3. }  
Java代碼  收藏代碼
  1. static {  
  2.     System.loadLibrary("hello-jni");  
  3. }  
 
注意這裏調用的共享庫名遵循Linux對庫文件的命名慣例,因爲OPhone的核心實際上是Linux系統——上例中,實際加載的庫文件應爲 “libhello-jni.so”,在引用時遵循命名慣例,不帶“lib”前綴和“.so”的擴展名。對於沒有按照上述慣例命名的Native庫,在加 載時仍需要寫成完整的文件名。
 
JNI函數的使用方法和普通Java函數一樣。在本例中,調用代碼如下:
 
  1. TextView tv =  new  TextView( this );  
  2. tv.setText( stringFromJNI() );  
  3. setContentView(tv);  
Java代碼  收藏代碼
  1. TextView tv = new TextView(this);  
  2. tv.setText( stringFromJNI() );  
  3. setContentView(tv);  
 
就可以在TextView中顯示出來自於Native函數的字符串。怎麼樣,是不是很簡單呢?
 
Native調用Java模塊
從OPhone的系統架構來看,JVM和Native系統庫位於內核之上,構成OPhone Runtime;更多的系統功能則是通過在其上的Application Framework以Java API的形式提供的。因此,如果希望在Native庫中調用某些系統功能,就需要通過JNI來訪問Application Framework提供的API。
 
JNI規範定義了一系列在Native代碼中訪問Java對象及其成員與方法的API。下面我們還是通過示例來具體講解。首先,新建一個SayHello 的類,代碼如下:
 
  1. package  com.example.hellojni;  
  2. public   class  SayHello {  
  3.         public  String sayHelloFromJava(String nativeMsg) {  
  4.                String str = nativeMsg + " But shown in Java!" ;  
  5.                return  str;  
  6.         }  
  7. }  
Java代碼  收藏代碼
  1. package com.example.hellojni;  
  2. public class SayHello {  
  3.         public String sayHelloFromJava(String nativeMsg) {  
  4.                String str = nativeMsg + " But shown in Java!";  
  5.                return str;  
  6.         }  
  7. }  
 
接下來要實現的就是在Native代碼中調用這個SayHello 類中的sayHelloFromJava方法。
 
一般來說,要在Native代碼中訪問Java對象,有如下幾個步驟:
1.         得到該Java對象的類定義。JNI定義了jclass 這個類型來表示Java的類的定義,並提供了FindClass接口,根據類的完整的包路徑即可得到其jclass 。
2.         根據jclass 創建相應的對象實體,即jobject 。在Java中,創建一個新對象只需要使用new 關鍵字即可,但在Native代碼中創建一個對象則需要兩步:首先通過JNI接口GetMethodID得到該類的構造函數,然後利用NewObject接口構造出該類的一個實例對象。
3.         訪問jobject 中的成員變量或方法。訪問對象的方法是先得到方法的Method ID,然後使用Call<Type>Method 接口調用,這裏Type對應相應方法的返回值——返回值爲基本類型的都有相對應的接口,如CallIntMethod;其他的返回值(包括String) 則爲CallObjectMethod。可以看出,創建對象實質上是調用對象的一個特殊方法,即構造函數。訪問成員變量的步驟一樣:首先 GetFieldID得到成員變量的ID,然後Get/Set<Type>Field讀/寫變量值。
 
上面概要介紹了從Native代碼中訪問Java對象的過程,下面我們結合示例來具體看一下。如下是調用sayHelloFromJava方法的Native代碼:
 
  1. jstring helloFromJava( JNIEnv* env ) {  
  2.        jstring str = NULL;  
  3.        jclass clz = (*env)->FindClass(env, "com/example/hellojni/SayHello" );  
  4.        jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>" ,  "()V" );  
  5.        jobject obj = (*env)->NewObject(env, clz, ctor);  
  6.        jmethodID mid = (*env)->GetMethodID(env, clz, "sayHelloFromJava" ,  "(Ljava/lang/String;)Ljava/lang/String;" );  
  7.        if  (mid) {  
  8.               jstring jmsg = (*env)->NewStringUTF(env, "I'm born in native." );  
  9.               str = (*env)->CallObjectMethod(env, obj, mid, jmsg);  
  10.        }  
  11.        return  str;  
  12. }  
Java代碼  收藏代碼
  1. jstring helloFromJava( JNIEnv* env ) {  
  2.        jstring str = NULL;  
  3.        jclass clz = (*env)->FindClass(env, "com/example/hellojni/SayHello");  
  4.        jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>""()V");  
  5.        jobject obj = (*env)->NewObject(env, clz, ctor);  
  6.        jmethodID mid = (*env)->GetMethodID(env, clz, "sayHelloFromJava""(Ljava/lang/String;)Ljava/lang/String;");  
  7.        if (mid) {  
  8.               jstring jmsg = (*env)->NewStringUTF(env, "I'm born in native.");  
  9.               str = (*env)->CallObjectMethod(env, obj, mid, jmsg);  
  10.        }  
  11.        return str;  
  12. }  
 
 
可以看到,上述代碼和前面講到的步驟完全相符。這裏提一下編程時要注意的要點:1、FindClass要寫明Java類的完整包路徑,並將 “.”以“/”替換;2、GetMethodID的第三個參數是方法名(對於構造函數一律用“<init>”表示),第四個參數是方法的“籤 名”,需要用一個字符串序列表示方法的參數(依聲明順序)和返回值信息。由於篇幅所限,這裏不再具體說明如何根據方法的聲明構造相應的“簽名”,請參考 JNI的相關文檔。
 
關於上面談到的步驟再補充說明一下:在JNI規範中,如上這種使用NewObject創建的對象實例被稱爲“Local Reference”,它僅在創建它的Native代碼作用域內有效,因此應避免在作用域外使用該實例及任何指向它的指針。如果希望創建的對象實例在作用 域外也能使用,則需要使用NewGlobalRef接口將其提升爲“Global Reference”——需要注意的是,當Global Reference不再使用後,需要顯式的釋放,以便通知JVM進行垃圾收集。
 
Native模塊的編譯與發佈
 
通過前面的介紹,我們已經大致瞭解了在OPhone的應用開發中使用JNI的方法。那麼,開發者如何編譯出能在OPhone上使用的Native模塊呢?編譯出的Native模塊又如何像APK文件那樣分發、安裝呢?
 
Google於2009年6月底發佈了Android NDK的第一個版本,爲廣大開發者提供了編譯用於Android應用的Native模塊的能力,以及將Native模塊隨Java應用打包爲APK文件, 以便分發和安裝的整套解決方案。NDK的全稱是Native Development Toolkit,即原生應用開發包。由於OPhone平臺也基於Android,因此使用Android NDK編譯的原生應用或組件完全可以用於OPhone。需要注意的是,Google聲稱此次發佈的NDK僅兼容於Android 1.5及以後的版本,由於OPhone 1.0平臺基於Android 1.5之前的版本,雖然不排除使用該NDK開發的原生應用或組件在OPhone 1.0平臺上正常運行的可能性,但建議開發者僅在OPhone 1.5及以上的平臺使用。
 
最新版本的NDK可以在http://developer.android.com/sdk/ndk/index.html 下載。NDK提供了適用於Windows、Linux和MAC OS X的版本,開發者可以根據自己的操作系統下載相應的版本。本文僅使用基於Linux的NDK版本做介紹和演示。
 
NDK的安裝很簡單:解壓到某個路徑下即可,之後可以看到若干目錄。其中docs目錄中包含了比較詳細的文檔,可供開發者參考,在NDK根目錄 下的README.TXT也對個別重要文檔進行了介紹;build目錄則包含了用於Android設備的交叉編譯器和相關工具,以及一組系統頭文件和系統 庫,其中包括libc、libm、libz、liblog(用於Android設備log輸出)、JNI接口及一個C++標準庫的子集(所謂“子集”是指 Android對C++支持有限,如不支持Exception及STL等);apps目錄是用於應用開發的目錄,out目錄則用於編譯中間結果的存儲。接 下來,我們就用前面的例子簡單講解一下NDK的使用。
 
進入<ndk>/apps目錄,我們可以看到一些示例應用,以hello-jni爲例:在hello-jni目錄中有一個 Application.mk文件和一個project文件夾,project文件夾中則是一個OPhone Java應用所有的工程文件,其中jni目錄就是Native代碼放置的位置。這裏Application.mk主要用於告訴編譯器應用所需要用到的 Native模塊有什麼,對於一般開發在示例提供的文件的基礎上進行修改即可;如果需要了解更多,可參考<ndk>/docs /APPLICATION-MK.txt。接下來,我們將示例文件與代碼如圖2放置到相應的位置:
 
 
 
圖2 Hello-JNI示例的代碼結構
 
可以看到,和Java應用一樣,Native模塊也需要使用Android.mk文件設置編譯選項和參數,但內容有較大不同。對於Native模塊而言,一般需要了解如下幾類標籤:
 
1.         LOCAL_MODULE:定義了在整個編譯環境中的各個模塊, 其名字應當是唯一的。此外,這裏設置的模塊名稱還將作爲編譯出來的文件名:對於原生可執行文件,文件名即爲模塊名稱;對於靜態/動態庫文件,文件名爲 lib+模塊名稱。例如hello-jni的模塊名稱爲“hello-jni”,則編譯出來的動態庫就是libhello-jni.so。
2.         LOCAL_SRC_FILES:這裏要列出所有需要編譯的C/C++源文件,以空格或製表符分隔;如需換行,可放置“\”符號在行尾,這和GNU Makefile的規則是一致的。
3.         LOCAL_CFLAGS:定義gcc編譯時的CFLAGS參數,與GNU Makefile的規則一致。比如,用-I參數可指定編譯所需引用的某個路徑下的頭文件。
4.         LOCAL_C_INCLUDES:指定自定義的頭文件路徑。
5.         LOCAL_SHARED_LIBRARIES:定義鏈接時所需要的共享庫文件。這裏要鏈接的共享庫並不限於NDK編譯環境中定義的所有模塊。如果需要引用其他的庫文件,也可在此處指定。
6.         LOCAL_STATIC_LIBRARIES:和上個標籤類似,指定需要鏈接的靜態庫文件。需要注意的是這個選項只有在編譯動態庫的時候纔有意義。
7.         LOCAL_LDLIBS:定義鏈接時需要引入的系統庫。使用時需要加-l前綴,例如-lz指的是在加載時鏈接libz這個系統庫。libc、libm和libstdc++是編譯系統默認會鏈接的,無需在此標籤中指定。
 
欲瞭解更多關於標籤類型及各類標籤的信息,可參考<ndk>/docs/ANDROID-MK.txt文件,其中詳細描述了Android.mk中各個標籤的含義與用法。如下給出的就是我們的示例所用的Android.mk:
 
 
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE    :=  hello-jni  
  4. LOCAL_C_INCLUDES :=  $(LOCAL_PATH)/include  
  5. LOCAL_SRC_FILES   :=  src/call_java.c \  
  6.                                           src/hello-jni.c   
  7. include $(BUILD_SHARED_LIBRARY)  
Java代碼  收藏代碼
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3. LOCAL_MODULE    :=  hello-jni  
  4. LOCAL_C_INCLUDES :=  $(LOCAL_PATH)/include  
  5. LOCAL_SRC_FILES   :=  src/call_java.c \  
  6.                                           src/hello-jni.c   
  7. include $(BUILD_SHARED_LIBRARY)  
 
 
寫好了代碼和Makefile,接下來就是編譯了。使用NDK進行編譯也很簡單:首先從命令行進入<ndk>目錄,執 行./build/host-setup.sh,當打印出“Host setup complete.”的文字時,編譯環境的設置就完成了。這裏開發者需要注意的是,如果使用的Linux發行版是Debian或者Ubuntu,需要通過 在<ndk>目錄下執行bash build/host-setup.sh,因爲上述兩個發行版使用的dash shell與腳本有兼容問題。接下來,輸入make APP=hello-jni,稍等片刻即完成編譯,如圖3所示。從圖中可以看到,在編譯完成後,NDK會自動將編譯出來的共享庫拷貝到Java工程的 libs/armeabi目錄下。當編譯Java工程的時候,相應的共享庫會被一同打包到apk文件中。在應用安裝時,被打包在libs/armeabi 目錄中的共享庫會被自動拷貝到/data/data/com.example.HelloJni/lib/目錄;當System.loadLibrary 被調用時,系統就可以在上述目錄尋找到所需的庫文件libhello-jni.so。如果實際的Java工程不在這裏,也可以手動在Java工程下創建 libs/armeabi目錄,並將編譯出來的so庫文件拷貝過去。
 
 
圖3 使用NDK編譯Hello-JNI
 
 
最後,將Java工程連帶庫文件一同編譯並在OPhone模擬器中運行,結果如圖4所示。
 
通過上面的介紹,你應該已經對OPhone上的Native開發有了初步瞭解,或許也已經躍躍欲試了。事實上,儘管Native開發在 OPhone上不具有Java語言的類型安全、兼容性好、易於調試等特性,也無法直接享受平臺提供的豐富的API,但JNI還是爲我們提供了更多的選擇, 使我們可以利用原生應用的優勢來做對性能要求高的操作,也可以利用或移植C/C++領域現有的衆多功能強大的類庫或應用,爲開發者提供了充分的施展空間。 這就是OPhone的魅力!
 
 
圖4 Hello-JNI在OPhone模擬器上的運行結果
參考文獻
[1]      Sheng Liang. Java Native Interface: Programmer's Guide and Specification.http://java.sun.com/docs/books/jni/ .
[2]      Sun Microsystems. Java Native Interface Specification v1.5.http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html .


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章