Android NDK开发入门学习笔记(图文教程,极其详尽)
以前也簡單用過JNI,但是只是簡單用一下,好多都不明白。最近在看源碼部分,有涉及到JNI調用的,所以這次打算徹底把它搞定。
先普及一下JNI的調用關系:JAVA------------------------>JNI------------------------------->Native.
我們需要從我們的入口代碼寫起,我們先來一段含有native函數的簡單類:
這段代碼就是說從Native中生成了一個Bean對象,然后將這個對象的tag屬性返回給調用者。
所以這里有兩個知識點可以學到:
1.如果生成一個自定義對象
2.如何在native中生成String字符串對象
Ok,既然Hello這個類有了createBean這個native方法,則我們需要使用Jdk提供的javah命令來生成C/C++所需要的頭文件,Javah命令的說明如下:
用法:javah [options] <classes> 其中, [options] 包括:-o <file> 輸出文件 (只能使用 -d 或 -o 之一)-d <dir> 輸出目錄-v -verbose 啟用詳細輸出-h --help -? 輸出此消息-version 輸出版本信息-jni 生成 JNI 樣式的標頭文件 (默認值)-force 始終寫入輸出文件-classpath <path> 從中加載類的路徑-bootclasspath <path> 從中加載引導類的路徑 <classes> 是使用其全限定名稱指定的 (例如, java.lang.Object)。在我們正常使用的時候只需要簡單的幾個參數即可,我們以Hello這個類來舉例說明: javah -d E:\Kongfuzi\HelloJNI com.sahadev.regix.Hello
javah最基本的,不需要多說。-d 前面的用法已經說明,用于指定輸出目錄,我這里的輸入目錄是:E:\Kongfuzi\HelloJNI,最后是我們要對那個類操作的全路徑名稱,必須寫全包名,最后需要注意的一點是,必須在com包名的上一級目錄執行,一般是src目錄,否則會出現如下錯誤: E:\Kongfuzi\HelloJNI\src\com>javah -d E:\Kongfuzi\HelloJNI com.sahadev.regix.Hello 錯誤: 找不到 'com.sahadev.regix.Hello' 的類文件。
上面是個錯誤的例子,我現在位于src的下級目錄com,注意不可以。
當這個命令執行完成之后,會在我們指定的目錄生成一個以.h結尾的頭文件:
打開它:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_sahadev_regix_Hello */#ifndef _Included_com_sahadev_regix_Hello #define _Included_com_sahadev_regix_Hello #ifdef __cplusplus extern "C" { #endif /** Class: com_sahadev_regix_Hello* Method: createBean* Signature: ()Lcom/sahadev/regix/Bean;*/ JNIEXPORT jobject JNICALL Java_com_sahadev_regix_Hello_createBean(JNIEnv *, jclass);#ifdef __cplusplus } #endif #endif這是一段C++代碼,上面的注釋說,不要去編輯這個文件,這是機器自動生成的。看看它為我們自動生成了什么:
紅色箭頭指出的便是我們在Java文件中自己定義的。好,接下來我們來實現它:
我們在工程中添加名為jni的文件夾(如果安裝了NDK支持的話,可以一鍵自動生成相關文件,這里就不多介紹了)。
然后在其中添加Android.mk、com_sahadev_regix_Hello.h,再新建一個名叫JNI_C++.cpp的文件:
打開JNI_C++.cpp:
寫入如下代碼:
//============================================================================ // Name : JNI_C++.cpp // Author : Sahadev // Version : // Copyright : Your copyright notice // Description : Hello World in C++, Ansi-style //============================================================================ #include <jni.h>//引入必須的JNI支持文件 #include "com_sahadev_regix_Main.h"//引入頭文件JNIEXPORT jobject JNICALL Java_com_sahadev_regix_Main_createBean(JNIEnv *env,jclass jc) {jclass jcl1 = env->FindClass("com/sahadev/regix/Bean"); //載入Bean類,注意寫全包名,jclass 即為JAVA中的Class,代表類jmethodID cMethodID = env->GetMethodID(jcl1, "<init>","(Ljava/lang/String;)V");//獲取構造方法的ID,第一個參數是從哪個類獲取,第二個參數<init>代表這是構造方法,第三個參數代表傳入的參數是String,返回值為Void,這里的寫法可以從JNI的文檔中找到return env->NewObject(jcl1, cMethodID,env->NewStringUTF("Hello,JNI!This is sahadev!"));//最后通過調用這個方法傳入一個jString的字符串 }我們將頭文件引入,然后拷貝頭文件中的native方法,添加參數變量,添加方法體。
上面的代碼后面有一部分注釋,這里補充一下:findClass和Java中的ClassLoader差不多,jclass等于Java中的Class,在這里調用方法的話需要先得到方法的ID,就像第二行使用了GetMethodID來獲取,這三個參數分別代表:獲取哪個類的方法,構造方法傳<init>普通方法寫方法名,第三個參數則代表參數個數類型返回值,最后通過NewStringUTF來生成jString,注意:在c++直接寫出的字符串它并不是Java中的字符串,必須通過NewStringUTF方法來實現,最后通過調用NewObject方法來調用Bean的構造方法,并傳入參數jString來生成一個Bean對象,并返回給調用者。具體資料請參見:JNI官方文檔
好,我們的C++文件寫好了,接下來就需要對它進行編譯了,這里的編譯工具不是什么GUN,而是Android官方提供的NDK-BUILD工具,如果是在Eclipse中集成了NDK環境,可以使用clean功能直接對它進行編譯,我們這里只介紹手動編譯:
首先我們需要編輯Android.mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JNI_C++ LOCAL_SRC_FILES := JNI_C++.cpp include $(BUILD_SHARED_LIBRARY)LOCAL_PATH即為調用命令的所在目錄,你在哪個目錄下使用cmd命令,這里就會返回它的路徑地址
LOCAL_MODULE你生成的文件名稱是什么,輸出之后會自動在名稱的前后加上lib和.so
LOCAL_SRC_FILES要對哪個文件進行編譯。
http://developer.android.com/intl/zh-cn/ndk/guides/android_mk.html官方文檔對上面的命令做了詳細解釋。
接下來,需要進入通過cmd命令進入工程所在的目錄,然后使用命令ndk-build編譯即可,如果ndk-build目錄沒有添加入本地變量,則使用全路徑訪問這個命令。我當時不知道這個命令如何使用,就進入了ndk-build所在的目錄,想看看它的幫助說明,結果出現了這樣的提示:
E:\Android_Sdk\android-ndk-r10e>ndk-build Android NDK: Could not find application project directory ! Android NDK: Please define the NDK_PROJECT_PATH variable to point to it. E:\Android_Sdk\android-ndk-r10e\build/core/build-local.mk:143: *** Android NDK: Aborting . Stop.說沒有發現應用的工程目錄,于是我就去build/core/build-local.mk文件中的143行查看是什么原因: ifndef NDK_PROJECT_PATHNDK_PROJECT_PATH := $(call find-project-dir,.,jni/Android.mk) endif ifndef NDK_PROJECT_PATHNDK_PROJECT_PATH := $(call find-project-dir,.,AndroidManifest.xml) endif ifndef NDK_PROJECT_PATH$(call __ndk_info,Could not find application project directory !)$(call __ndk_info,Please define the NDK_PROJECT_PATH variable to point to it.)$(call __ndk_error,Aborting) endif
根據上下文,應該是沒有找到jni/Android.mk或者AndroidManifest.xml之類的文件,再根據上下文發現有這么一段說明: # ==================================================================== # # If NDK_PROJECT_PATH is not defined, find the application's project # path by looking at the manifest file in the current directory or # any of its parents. If none is found, try again with 'jni/Android.mk' # # Note that we first look at the current directory to avoid using # absolute NDK_PROJECT_PATH values. This reduces the length of all # source, object and binary paths that are passed to build commands. # # It turns out that some people use ndk-build to generate static # libraries without a full Android project tree. # # If NDK_PROJECT_PATH=null, ndk-build make no attempt to look for it, but does # need the following variables depending on NDK_PROJECT_PATH to be explicitly # specified (from the default, if any): # # NDK_OUT # NDK_LIBS_OUT # APP_BUILD_SCRIPT # NDK_DEBUG (optional, default to 0) # Other APP_* used to be in Application.mk # # This behavior may be useful in an integrated build system. # # ====================================================================
也就是說如果你沒有定義NDK_PROJECT_PATH的話,它就會根據manifest文件去尋找工程路徑,這里的NDK_PROJECT_PATH你可以在環境變量中指定:
這樣的話,就可以在任何地方直接使用ndk-build命令對工程目錄進行編譯了,設置完成之后請重啟cmd:
好,編譯完成之后我們就可以在我們工程目錄的obj目錄下發現編譯好的.so等相關文件:
注意,我們在Android.mk文件中定義的LOCAL_MODULE是JNI_C++,而這里的輸出文件會為它自動加上lib前綴與.so后綴。
如果集成了NDK環境,在Clean的時候會自動進行編譯:
編譯完成之后,就需要看看如何使用它了。
在使用的Java文件Hello中添加如下靜態代碼塊:
static {System.loadLibrary("JNI_C++");}在這里的JNI_C++便是我們在.mk文件中定義好的,在使用它的時候,它會自動為我們加上lib前綴與.so后綴,以便訪問我們的.so文件,注意,千萬別自己加lib*.so,以下是Runtime.loadLibrary的方法說明: Given the name "MyLibrary", that string will be passed to System.mapLibraryName. That means it would be a mistake for the caller to include the usual "lib" prefix and ".so" suffix.
在Activity中調用Hello這個類的getStringFromNative方法,這個方法便可以將從native中生成的字符串對象、Bean對象返回。
效果:
快試試,遇到什么問題歡迎留言。
總結
以上是生活随笔為你收集整理的Android NDK开发入门学习笔记(图文教程,极其详尽)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 咨询笔记:麦肯锡7步成诗
- 下一篇: 【LeetCode】3月27日打卡-Da