Android提供NDK開發包來提供Android平臺的C++開發,用來擴展Android SDK的功能。主要包括Android NDK構建系統和JNI實現與原生代碼通信兩部分。 一、Android NDK構建系統 1.1 構建庫 Android NDK的構建系統是基於GNU Make的。Andro ...
Android提供NDK開發包來提供Android平臺的C++開發,用來擴展Android SDK的功能。主要包括Android NDK構建系統和JNI實現與原生代碼通信兩部分。
一、Android NDK構建系統
1.1 構建庫
Android NDK的構建系統是基於GNU Make的。Android GNU Make系統除了需要一些內部的GNU片段外,還需要兩個文件:Android.mk和Application.mk。Android NDK源碼給了很多的例子,以HelloJni為例,Android.mk源碼:
#Android.mk必須以LOCAL_PATH變數開頭
LOCAL_PATH := $(call my-dir) #清除除了LOCAL_PATH以外的LOCAL_<name>變數,例如LOCAL_MODULE與LOCAL_SRC_FILES等 include $(CLEAR_VARS) #每一個原生組件被稱為一個模塊 LOCAL_MODULE := hello-jni
#源文件 LOCAL_SRC_FILES := hello-jni.c #編譯為共用庫,即尾碼名為.so include $(BUILD_SHARED_LIBRARY)
Application.mk源碼:
#一般選擇APP_ABI := armeabi-v7a就夠了
APP_ABI := all
為了建立可供主應用程式使用的模塊,必須將該模塊變成共用庫。按照上述必不可少的步驟,可以繼續編譯多個共用庫。
Android也可以編譯靜態庫(尾碼名為.a),但是實際的Android應用程式並不直接使用靜態庫,並且應用程式包中也不包含靜態庫。靜態庫可以用來構建共用庫。但是,當靜態庫與多個共用庫相連時,應用程式包中會包含靜態庫的多個副本,徒增應用程式包的大小。這種情況下,可以不構建靜態庫,而是將通用模塊作為共用庫建立起來,動態連接依賴模塊以消除重覆的副本。如下Android.mk實現的是共用庫之間的代碼共用。
LOCAL_PATH := $(call my-dir) #第三方AVI庫 include $(CLEAR_VARS) LOCAL_MODULE := avilib LOCAL_SRC_FILES := avilib.c include $(BUILD_SHARED_LIBRARY) #原生模塊1 include $(CLEAR_VARS) LOCAL_MODULE := module1 LOCAL_SRC_FILES := module1.c LOCAL_SHARED_LIBRARIES := avilib include $(BUILD_SHARED_LIBRARY) #原生模塊2 include $(CLEAR_VARS) LOCAL_MODULE := module2 LOCAL_SRC_FILES := module2.c LOCAL_SHARED_LIBRARIES := avilib include $(BUILD_SHARED_LIBRARY)
1.2 Prebuilt庫
共用模塊編譯時要求有源代碼,為此Android提供了Prebuilt庫,以下場合,Prebuilt庫是非常有用的:
- 想在不發佈源代碼的情況下將你的模塊發佈給他人;
- 想使用共用模塊的預建版來加速構建過程。
其他構建系統變數:
LOCAL_CFLAGS:一組可選的編譯器標誌,在編譯C和C++源文件的時候會被傳送給編譯器;
LOCAL_CPP_FLAGS:一組可選的編譯器標誌,在只編譯C++源文件時被傳送給編譯器;
LOCAL_LDLIBS:鏈接標誌的可選列表,它主要用於傳送要進行動態鏈接的系統庫列表。如鏈接日誌庫:
LOCAL_LDFLAGS += -llog
APP_CPPFLAGS:編譯器標誌,在編譯任何模塊的C++源文件時這些標誌都會被傳送給編譯器。
nkd-build腳本命令:
ndk-build –C /project path ndk-build –B ndk-build clean
二、JNI實現與原生代碼通信
Java層代碼如下:
public class HelloJni extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ public native String stringFromJNI(); /* This is another native method declaration that is *not* * implemented by 'hello-jni'. This is simply to show that * you can declare as many native methods in your Java code * as you want, their implementation is searched in the * currently loaded native libraries only the first time * you call them. * * Trying to call this function will result in a * java.lang.UnsatisfiedLinkError exception ! */ public native String unimplementedStringFromJNI(); /* this is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.example.hellojni/lib/libhello-jni.so at * installation time by the package manager. */ static { System.loadLibrary("hello-jni"); } }
JNI代碼如下,其中Java方法stringFromJNI不帶任何參數,但是原生方法帶兩個參數:
jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); }
第一個參數JNIEnv是指向可用JNI函數表的藉口指針;第二個參數jobjects是HelloJni類實例的Java對象引用。
這裡註意C與C++代碼稍有不同,C代碼如下:
return (*env)->NewStringUTF(env, "Hello from JNI ! ");
C++代碼如下:
return env->NewStringUTF("Hello from JNI ! ");
這是因為C++代碼中,JNIEnv實際上是一個C++類實例,JNI函數以成員函數的形式存在,因此JNI方法調用不要求JNIEnv實例作參數。
2.1 C/C++ 頭文件生成器:javah
JDK自帶一個名為javah的命令行工具,該工具由Java類文件的原始定義生成原生函數名及其參數列表,這樣程式員避免編寫繁雜多餘的定義。C/C++源文件只需要包含這個頭文件並提供原生方法實現。
javah的參數列表如下:
C:\Users\jiayayao>javah
用法:
javah [options] <classes>
其中, [options] 包括:
-o <file> 輸出文件 (只能使用 -d 或 -o 之一)
-d <dir> 輸出目錄
-v -verbose 啟用詳細輸出
-h --help -? 輸出此消息
-version 輸出版本信息
-jni 生成 JNI 樣式的標頭文件 (預設值)
-force 始終寫入輸出文件
-classpath <path> 從中載入類的路徑
-cp <path> 從中載入類的路徑
-bootclasspath <path> 從中載入引導類的路徑
<classes> 是使用其全限定名稱指定的
(例如, java.lang.Object)。
生成頭文件的命令行參數如下:
javah -classpath bin/classes com.example.hellojni.Hellojni
2.2 數據類型
Java有兩種數據類型:基本數據類型和引用數據類型:基本數據類型中Java/JNI/C++的映射關係如下:
引用類型的類型映射關係如下:
引用類型以不透明的引用方式傳遞給原生代碼,而不是以原生數據類型的的形式呈現,因此引用類型不能直接使用和修改。JNI提供了與這些引用類型密切相關的一組API。
字元串操作:
// 創建字元串 jstring javaString; javaString = (*env)->NewStringUTF(env, "hello world!"); // 記憶體溢出時,會返回NULL,註意判空 // 將Java字元串轉換成C字元串 const jbyte* str; jboolean isCopy; str = (*env)->GetStringUTFChars(env, javaString, &isCopy); if (0 != str) { } // 釋放字元串 (*env)->ReleaseStringUTFChars(env, javaString, str);
數組操作:
// 創建數組 jintArray javaArray; javaArray = (*env)->NewIntArray(env, 10); if(0!=javaArray) { }
// Get<Type>ArrayRegion函數將給定的基本Java數組複製到給定的C數組中 // 將Java數組區複製到C數組中 jint nativeArray[10]; (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray); // 從C數組向Java數組提交所做的修改 (*env)->SetIntArrayRegion(env, javaArray, 0, 10, nativeArray);
原生方法的記憶體分配超出了虛擬機的管理範圍,且不能用虛擬機的垃圾回收器回收原生方法中的記憶體。
原生代碼回到Java損耗性能,建議將所有需要的參數傳遞給原生代碼調用,而不是讓原生代碼回到Java中。
先記錄這麼多,以後接著補充。