前言
Android系統中的應用程式都是用Java開發的。Android NDK使我們能夠在android上使用C/C++開發的原生代碼。有兩個理由使用NDK: 一是合理的重用現有的代碼;二是在程式中某些關鍵的部分提高執行效率。
這裡先講幾個符號的約定:
<project> - 你的Android應用程式工程的目錄
<ndk> - 你的ndk安裝的目錄
捷徑
這裡先扯一句題外話 -- 如果你不需要使用NDK開發,只是需要使用第三方用NDK開發的庫,那麼你只需要這樣做:
把第三方提供的libxxx.so放到你的<project>/libs/armeabi/下, 然後在程式隨便什麼地方中加入
static {
System.loadLibray("xxx");
}
就可以使用該庫了。
安裝NDK
NDK的安裝很簡單:
1. 首先要將SDK升級至最新,然後下載ndk(可能要翻#和諧#牆,恩恩)將它解壓到某個目錄<ndk>下。
2. 運行: <ndk>/build/host-setup.sh
3. 如果成功的話就OK了,如果失敗的話檢查一下你是不是下載了正確的ndk版本(例如你的作業系統是linux而下載了windows版的ndk).
Java部分
現在我們用一個很簡單的例子來說明NDK的使用。我們在eclipse中建立一個android工程,其中:
Project Name:jnitest
Build Target: Android 1.6
Application Name: JNI Test
Package Name: org.eshock.jnitest
Create Activity: JNITest
JNITest.java:
package org.eshock.jnitest;
import android.app.Activity;
import android.os.Bundle;
public class JNITest extends Activity {
public native int plus (int x, int y);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int x = plus(1, 2);
android.util.Log.d("jni", String.valueOf(x));
}
static {
System.loadLibrary("mylib");
}
}
我們只是示範NDK,所以就不要介面了。在這個程式中,我們調用一個c語言編寫的plus(int, int)函數來計算1+2的值,然後在log中列印它。這個庫的名稱叫做mylib。
要使用一個c語言的函數,需要在java中聲明:
public native int plus(int x, int y);
這樣java編譯器就知道這個函數是外部庫中實現的。
C部分
接下來我們使用C語言實現這個plus函數。
建立<project>/jni/mylib.c:
#include <string.h>
#include <jni.h>
JNIEXPORT jint JNICALL
Java_org_eshock_jnitest_JNITest_plus( JNIEnv* env,
jobject thiz,
jint x,
jint y )
{
return x + y;
}
這 裡我們看到,mylib.c裡的plus函數比java裡面的plus函數的簽名要複雜了很多,主要是plus前面增加了包名,函數中多了兩個參數,以及 所有的int變成了jint。關於c中類型與jni中類型的對應關係,可以參見jni的相關文檔,例如core Java II中的最後一章。如果你不想去查閱文檔,有一個方法:
首先隨便在某個目錄下建立一個檔案JNITest.java:
public class JNITest {
public native int plus (int x, int y);
}
然後在命令列下執行:
javac JNITest.java && javah JNITest
將產生一個檔案JNITest.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */
#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest
* Method: plus
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_JNITest_plus
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
這裡自動產生了java中native函數對應的簽名,你可以在c代碼中使用它。(記得修改包名)。
Android.mk和Application.mk
Android.mk是一個makefile,用來告訴NDK需要編譯哪些檔案,產生哪些模組。我們建立<jni>/Android.mk檔案:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := mylib.c
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_PATH表示c原始碼檔案的位置;LOCAL_MODULE表示產生的共用庫的名稱;LOCAL_SRC_FILES代表c代碼的檔案。不需要把標頭檔列在裡面;標頭檔的依賴關係是ndk自動計算的。
Application.mk的作用是告訴Android SDK需要哪些庫檔案。有了它,NDK就可以把庫放在正確的位置。我們建立<project>/Application.mk:
APP_PROJECT_PATH := $(call my-dir)
APP_MODULES := mylib
APP_PROJECT_PATH代表android工程所在目錄,在本例中它被放在<project>中; APP_MODULES表示工程需要的庫,如果有多個以空格分開。
編譯共用庫
由於ndk規定Application.mk必須放在<ndk>/apps/project_name,我們在<ndk>下建立一個符號串連:
ln -s <project> <ndk>/apps/jnitest
這樣,我們就可以開始編譯共用庫了:
在<ndk>目錄下運行:
make APP=jnitest
Android NDK: Building for application 'jnitest'
Compile thumb : mylib <= apps/jnitest/jni/mylib.c
SharedLibrary : libmylib.so
Install : libmylib.so => apps/jnitest/libs/armeabi
可以看到編譯成功,並在<project>/libs/armeabi/下產生了libmylib.so。
測試
在運行之前,首先確認一下你的工程裡面各個檔案都齊全,應該類似於這樣:
然後測試,正常的話應該能看到log中有輸出3.