【Android 開發技巧】NDK和JNI的使用范例
概述
Android系統的底層庫由c/c++編寫,上層Android應用程序通過Java虛擬機調用底層接口,銜接底層c/c++庫與Java應用程序間的接口正是JNI(Java Native Interface)。本文將描述如何在Ubuntu下配置Android JNI的開發環境,以及如何編寫一個簡單的c函數庫和JNI接口,并通過編寫Java程序調用這些接口,最終運行在模擬器上的過程。
環境配置
1. 安裝jdk 1.6
(1) 從jdk官方網站http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u29-download-513648.html
下載jdk-6u29-linux-i586.bin文件。
(2) 執行jdk安裝文件
$ chmod a+x jdk-6u29-linux-i586.bin
$ jdk-6u29-linux-i586.bin
(3) 配置jdk環境變量
$ sudo vim /etc/profile
#JAVA EVIRENMENT
export JAVA_HOME=/usr/lib/java/jdk1.6.0_29
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
保存后退出編輯,并重啟系統。
(4) 驗證安裝
$ java -version
java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
Java HotSpot(TM) Server VM (build 20.4-b02, mixed mode)
$ javah
用法:javah [選項] <類>
其中[選項] 包括:
-help 輸出此幫助消息并退出
-classpath <路徑> 用于裝入類的路徑
-bootclasspath <路徑> 用于裝入引導類的路徑
-d <目錄> 輸出目錄
-o <文件> 輸出文件(只能使用-d 或-o 中的一個)
-jni 生成JNI樣式的頭文件(默認)
-version 輸出版本信息
-verbose 啟用詳細輸出
-force 始終寫入輸出文件
使用全限定名稱指定<類>(例如,java.lang.Object)。
2. 安裝Android開發環境
ubuntu下安裝android應用程序開發環境與windows類似, 依次安裝好以下軟件即可:
(1) Eclipse
(2) AVD
(3) Android SDK
與windows下安裝唯一不同的一點是,下載這些軟件的時候要下載Linux版本的安裝包。
安裝好以上android應用程序的開發環境后,還可以選擇是否需要配置emulator和adb工具的環境變量,以方便在進行JNI開發的時候使用。
配置步驟如下:
把emulator所在目錄android-sdk-linux/tools以及adb所在目錄android-sdk-linux/platform-tools添加到環境變量中,android-sdk-linux指android sdk安裝包android-sdk_rxx-linux的解壓目錄。
$ sudo vim /etc/profile
export PATH=~/software/android/android-sdk-linux/tools:$PATH
export PATH=~/software/android/android-sdk-linux/platform-tools:$PATH
編輯完畢后退出,并重啟生效。
3. 安裝NDK
NDK是由android提供的編譯android本地代碼的一個工具。
(1) 從android ndk官網http://developer.android.com/sdk/ndk/index.html下載ndk,目前最新版本為android-ndk-r6b-linux-x86.tar.bz2.
(2) 解壓ndk到工作目錄:
$ tar -xvf android-ndk-r6b-linux-x86.tar.bz2
$ sudo mv android-ndk-r6b /usr/local/ndk
(3) 設置ndk環境變量
$ sudo vim /etc/profile
export PATH=/usr/local/ndk:$PATH
編輯完畢后保存退出,并重啟生效。
(4) 驗證安裝
$ cd /usr/local/ndk/samples/hello-jni/
$ ndk-build
Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup : libs/armeabi/gdb.setup
Install : libhello-jni.so => libs/armeabi/libhello-jni.so
JNI實現
我們需要定義一個符合JNI接口規范的c/c++接口,這個接口不用太復雜,例如輸出一個字符串。接下來,則需要把c/c++接口的代碼文件編譯成共享庫(動態庫).so文件,并放到模擬器的相關目錄下。最后,啟動Java應用程序,就可以看到最終效果了。
3.1.編寫Java應用程序代碼
(1) 啟動Eclipse,新建android工程
Project:JNITest
Package:org.tonny.jni
Activity:JNITest
(2) 編輯資源文件
編輯res/values/strings.xml文件如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, JNITestActivity!</string> <string name="app_name">JNITest</string> <string name="btn_show">Show</string> </resources>
編輯res/layout/activity_jnitest.xml文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <EditText android:id="@+id/ed_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" /> <Button android:id="@+id/btn_show" android:layout_width="109dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/btn_show" /> </LinearLayout>
在主界面上添加了一個EditText控件和一個Button控件。
(3) 編輯JNITest.java文件
public class JNITest extends Activity { static { System.loadLibrary("JNITest"); } private native String GetReply(); private EditText edtName; private Button btnShow; String reply; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_jnitest); reply = GetReply(); edtName = (EditText) this.findViewById(R.id.ed_name); btnShow = (Button) this.findViewById(R.id.btn_show); btnShow.setOnClickListener(new Button.OnClickListener() { public void onClick(View arg0) { edtName.setText(reply); } }); } }
看這一段代碼:
static { System.loadLibrary("JNITest"); }
static表示在系統第一次加載類的時候,先執行這一段代碼,在這里表示加載動態庫libJNITest.so文件。
再看這一段:
private native String GetReply();
native表示這個方法由本地代碼定義,需要通過jni接口調用本地c/c++代碼。
btnShow.setOnClickListener(new Button.OnClickListener() { public void onClick(View arg0) { edtName.setText(reply); } });
這段代碼表示點擊按鈕后,把native方法的返回的字符串顯示到EditText控件。
(4) 編譯工程,生成.class文件。
3.2.用javah工具生成符合JNI規范的C語言頭文件
在終端中,進入android工程所在的bin目錄
$ cd ~/project/Android/JNITest/bin
用ls命令查看,可以看到bin目錄下有個classes目錄,其目錄結構為classes/org/tonny/jni,即classes的子目錄結構是android工程的包名org.tonny.jni。
注意,準備執行javah命令的時候,必須進入到org/tonny/jni的上級目錄,即classes目錄,否則javah會提示找不到相關的java類。
下面繼續:
$ cd classes
$ javah org.tonny.jni.JNITest
$ ls
org org_tonny_jni_JNITest.h
執行javah org.tonny.jni.JNITest命令,在classes目錄下會生成org_tonny_jni_JNITest.h 頭文件。如果不進入到classes目錄下的話,也可以這樣:
$ javah -classpath ~/project/Android/JNITest/bin/classes org.tonny.jni.JNITest
-classpath 參數表示裝載類的目錄。
3.3.編寫c/c++代碼
生成org_tonny_jni_JNITest.h 頭文件后,就可以編寫相應的函數代碼了。在android工程目錄下新建jni目錄,即~/project/Android/JNITest/jni,把org_tonny_jni_JNITest.h頭文件拷貝(剪切)到jni目錄下,并在jni目錄下新建org_tonny_jni_JNITest.c文件,編輯代碼如下:
#include<jni.h> #include<string.h> #include"org_tonny_jni_JNITest.h" JNIEXPORT jstring JNICALL Java_org_tonny_jni_JNITest_GetReply (JNIEnv *env, jobject obj){ return (*env)->NewStringUTF(env,(char*)"Hello,JNITest"); }
可以看到,該函數的實現相當簡單,返回一個字符串為:"Hello,JNITest"
3.4.編寫Android.mk文件
在~/project/Android/JNITest/jni目錄下新建Android.mk文件,android可以根據這個文件的編譯參數編譯模塊。編輯Android.mk文件如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libJNITest LOCAL_SRC_FILES := org_tonny_jni_JNITest.c include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE 表示編譯的動態庫名稱
LOCAL_SRC_FILES 表示源代碼文件
3.5.用ndk工具編譯并生成.so文件
進入到JNITest的工程目錄,執行ndk-build命令即可生成libJNITest.so文件。
$ cd ~/project/Android/JNITest/
$ ndk-build
Invalid attribute name:
package
Install : libJNITest.so => libs/armeabi/libJNITest.so
可以看到,在工程目錄的libs/armeabi目錄下生成了libJNITest.so文件。
3.6.在模擬器上運行
(1) 首先,把android模擬器啟動起來。進入到emulator所在目錄,執行emulator命令:
$ cd ~/software/android/android-sdk-linux/tools
$ ./emulator @AVD-2.3.3-V10 -partition-size 512
AVD-2.3.3-V10表示你的模擬器名稱,與在Eclipse->AVD Manager下的AVD Name對應,-partition-size表示模擬器的存儲設備容量。
(2) 接下來,需要把libJNITest.so文件拷貝到模擬器的/system/lib目錄下,執行以下命令:
$ cd ~/project/Android/JNITest/libs/armeabi/
$ adb remount
$ adb push libJNITest.so /system/lib
80 KB/s (10084 bytes in 0.121s)
當在終端上看到有80 KB/s (10084 bytes in 0.121s) 傳輸速度等信息的時候,說明拷貝成功。
(3) 在Eclipse下,右鍵點擊JNITest工程,Run As->Android Application,即可在模擬器上啟動程序。
在模擬器上點擊【Show】按鈕,即可看到Hello,JNITest,而這個字符串正是在org_tonny_jni_JNITest.c代碼文件中所定義的。
3.7 在真機上運行
在真機上運行,沒有在模擬器上面這么麻煩。需要注意的是,需要把app卸載,然后再重新安裝一遍,否則so文件有可能不會被編譯到apk中。
轉自自http://blog.csdn.net/manoel/article/details/39477295
源碼下載
http://pan.baidu.com/s/1qW5Ckgs
參考資料
http://www.2cto.com/kf/201111/111053.html
http://www.2cto.com/kf/201111/111052.html
http://bbs.51cto.com/thread-948244-1.html