【Android 開發技巧】NDK和JNI的使用范例

fyuuu 8年前發布 | 12K 次閱讀 Android開發 移動開發

 

概述

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


 本文由用戶 fyuuu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!