Android应用---基于NDK的samples例程hello-jni学习NDK开发
Android應用---基于NDK的samples例程hello-jni學習NDK開發
?
NDK下載地址:http://developer.android.com/tools/sdk/ndk/index.html,下載解壓為android-ndk-r10b,此目錄下的samples\hello-jni就是NDK自帶的例子,先來看編譯前后和用eclipse導入前后目錄的差別圖:
圖1
(1)??解壓后原始的目錄見圖1左邊的目錄結構。
(2)??進入D:\ADT-bundle\android-ndk-r10b\samples\hello-jni目錄,用ndk-build編譯會生成libs和obj目錄。
(3)??打開Eclipse,導入hello-jni后新增.settings、assets、bin和gen文件夾,以及.classpath與.project文件。
?
?
采用NDK開發APP部分功能的APK工程的目錄結構介紹如下,新建Android工程后,右擊工程名字,選擇Android Tools--->Add Native Support可增加C/C++來開發APP的部分的功能。
?
包名:com.example.hellojni,類名:HelloJni
?
1.?????.classpath和.project文件
從圖1可知,用Eclipse導入hello-jni工程時會生成這兩個文件,Eclipse將項目數據就是保存在這兩個文件中。
?
?
1.1? .project文件
.project文件保存關于項目中包含哪些文件、如何使用它們、如何構建項目等方面的信息,以及更多的詳細信息。
<?xmlversion="1.0" encoding="UTF-8"?> <projectDescription> <!-- 工程名稱 --><name>HelloJni</name> <!—工程注釋描述 --><comment></comment><projects></projects><!—builder指定 --><buildSpec><buildCommand><name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name><arguments></arguments></buildCommand><buildCommand><name>com.android.ide.eclipse.adt.PreCompilerBuilder</name><arguments></arguments></buildCommand><buildCommand><name>org.eclipse.jdt.core.javabuilder</name><arguments></arguments></buildCommand><buildCommand><name>com.android.ide.eclipse.adt.ApkBuilder</name><arguments></arguments></buildCommand></buildSpec><natures><nature>com.android.ide.eclipse.adt.AndroidNature</nature><nature>org.eclipse.jdt.core.javanature</nature></natures> </projectDescription>運行時需要的額外Eclipse插件<natures></natures>,及其具體加載方式信息<buildSpec></buildSpec>
?
Project Buildersand Natures的詳細介紹,請參考:
http://www.eclipse.org/articles/Article-Builders/builders.html
?
1.2? .classpath文件
.classpath的位置定義了你這個項目在編譯時所使用的$CLASSPATH,設置Classpath的目的,在于告訴Java執行環境,在哪些目錄下可以找到您所要執行的Java程序所需要的類或者包。
?
<?xmlversion="1.0" encoding="UTF-8"?> <classpath><classpathentry kind="src"path="src"/><classpathentry kind="src"path="gen"/><classpathentry kind="con"path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/><classpathentryexported="true" kind="con"path="com.android.ide.eclipse.adt.LIBRARIES"/><classpathentryexported="true" kind="con"path="com.android.ide.eclipse.adt.DEPENDENCIES"/><classpathentrykind="output" path="bin/classes"/> </classpath>從數據上我們容易看出,上面描述了工程的依賴文件:
(1)??kind="src",指定源文件位置, 對應工程屬性Java build path中Source項中的一項, kind="src" 指明為源文件, 源文件路徑path,如HelloJni.java保存在i\src\com\example\hellojni下面,而自動生成的BuildConfig.java和R.java保存在i\gen\com\example\hellojni目錄下。
圖2
?
?
(2)??kind="con",con指container,就是程序運行的容器,或者說是運行環境也可以。
(3)??kind="output",表示生成的.class文件,比如HelloJni.class存放的路徑,見圖2。
(4)??exported="true"表示導入第三方庫,但這里實際上沒有導入,見下圖:
圖3
?
總體上說這個文件就是配置整個工程的運行環境。
?
Eclipse打開hello-jni工程,默認情況下是看不到.classpath和.project文件的,需要
?
圖4
不選擇.*resources就OK了。
?
?
2.?????AndroidManifest.XML
<?xmlversion="1.0" encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.example.hellojni"android:versionCode="1"android:versionName="1.0"><uses-sdkandroid:minSdkVersion="3" /><applicationandroid:label="@string/app_name"android:debuggable="true"><activityandroid:name=".HelloJni"android:label="@string/app_name"><intent-filter><action android:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application> </manifest>這是應用程序HelloJni的配置文件,它所用的Activity組件HelloJni需要在這里配置才能使用。
?
3.?????project.properties文件
主要內容如下:
# Project target. target=android-19定義了目標平臺的API版本,此文件是由Android工具自動生成,不能手動修改。
?
4.?????.settings和assets文件夾
.settigns文件夾下只有org.eclipse.jdt.core.prefs這文件,內容如下:
eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.source=1.61.6這里應該表示是JDK的版本,Windows->Preferences-->java->Compiler-->compilercompliance level可以設置,但是設置后并不會修改此文件。
?
Assets文件夾是空的。
?
5.?????src\com\example\hellojni\HelloJni.java
packagecom.example.hellojni;importandroid.app.Activity; importandroid.widget.TextView; importandroid.os.Bundle;public classHelloJni extends Activity {/** Called when the activity is firstcreated. */@Overridepublic void onCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);/* Create a TextView and set itscontent.* the text is retrieved by calling anative* function.*/TextView tv = new TextView(this);tv.setText( stringFromJNI() );setContentView(tv);}/* A native method that is implemented bythe* 'hello-jni' native library, which ispackaged* with this application.*/public native String stringFromJNI();/* This is another native methoddeclaration that is *not** implemented by 'hello-jni'. This issimply to show that* you can declare as many native methodsin your Java code* as you want, their implementation issearched in the* currently loaded native libraries onlythe first time* you call them.** Trying to call this function will resultin 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 beenunpacked into*/data/data/com.example.hellojni/lib/libhello-jni.so at* installation time by the packagemanager.*/static {System.loadLibrary("hello-jni");} }這里說明兩點:
(1)??聲明JNI辦法
public nativeString stringFromJNI(); public nativeString unimplementedStringFromJNI();可以看到這兩個辦法的聲明中有native關鍵字,表示為本地辦法,也就是說這兩個辦法是通過本地代碼C/C++來實現的。
?
(2)??加載動態鏈接庫
static {System.loadLibrary("hello-jni"); }根據注釋,可以知道在APP啟動時會調用此代碼來加載hello-jni動態鏈接庫(完整名字為libhello-jni.so)。libhello-jni.so在被加載之前已經被放入到/data/data/com.example.HelloJni/lib/目錄下。
?
static區聲明的代碼會先于onCreate方法執行。如果你的程序中有多個類,而且如果HelloJni這個類不是你應用程序的入口,那么hello-jni這個庫會在第一次使用HelloJni這個類的時候加載。
?
JAVA通過JNI調用本地方法,而本地方法是以庫文件的形式存放的(在WINDOWS平臺上是DLL文件形式,在Android上是SO文件形式
?
?
6.?????Jni下的源代碼文件
從第5部分我們知道在HelloJni.java聲明了JNI方法,但是需要用C/C++來實現的,可通過javah命令來生成對應的頭文件,然后再根據頭文件來寫C/C++代碼。
?
6.1? 生成相應的.h文件
參考http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html,進入hello-jni目錄下,用命令javah -classpath bin -d jnicom.example.hellojni.HelloJni
-classpath bin:表示類的路勁
-d jni: 表示生成的頭文件存放的目錄
com.example.hellojni.HelloJni則是完整類名
?
這一步的成功要建立在已經在 bin/com/example/hellojni/? 目錄下生成了 HelloJni.class的基礎之上(編譯該JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就會生成C/C++的頭文件)。
?
按照這個辦法,編譯出錯:提示無法找到com.example.hellojni.HelloJni類文件,原因是我bin目錄下還有一層目錄 classes,然后改為下面的命令:
javah -classpathbin/classes -d jni com.example.hellojni.HelloJni
?
還是出錯,提示無法找到android.app.Activity的類文件,參考:
http://blog.csdn.net/hejinjing_tom_com/article/details/8125648, 最后成功生成.h頭文件的命令為:javah -classpath “D:\ADT-bundle\sdk\platforms\android-19\android.jar”;bin/classes-d jni com.example.hellojni.HelloJni,如下圖:
圖5
在\jni文件及下生成com_example_hellojni_HelloJni.h文件。
/* DO NOT EDITTHIS FILE - it is machine generated */ #include<jni.h> /* Header forclass com_example_hellojni_HelloJni */#ifndef_Included_com_example_hellojni_HelloJni #define_Included_com_example_hellojni_HelloJni #ifdef __cplusplus extern"C" { #endif #undefcom_example_hellojni_HelloJni_MODE_PRIVATE #definecom_example_hellojni_HelloJni_MODE_PRIVATE 0L … /** Class: com_example_hellojni_HelloJni* Method: stringFromJNI* Signature: ()Ljava/lang/String;*/ JNIEXPORT jstringJNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv *, jobject);/** Class: com_example_hellojni_HelloJni* Method: unimplementedStringFromJNI* Signature: ()Ljava/lang/String;*/ JNIEXPORT jstringJNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI(JNIEnv *, jobject);#ifdef __cplusplus } #endif #endif可以看到java定義的JNI方法stringFromJNI(),對應在C/C++方法是Java_com_example_hellojni_HelloJni_stringFromJNI,名字很長,不過比較有規律,按照java_pacakege_class_method 形式來命名,這里JNIEXPORT和JNICALL都是JNI的關鍵字,表示此函數是要被JNI調用的。
?
Signature:()Ljava/lang/String;()表示函數的參數為空(這里為空是指除了JNIEnv *, jobject 這兩個參數之外沒有其他參數,JNIEnv*, jobject是所有jni函數必有的兩個參數,分別表示jni環境和對應的java類(或對象)本身),Ljava/lang/String; 表示函數的返回值是java的String對象。
?
6.2? hello-jni.c
#include<string.h> #include<jni.h>/* This is a trivialJNI example where we use a native method* to return a new VM String. See thecorresponding Java source* file located at:** apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java*/ jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env,jobject thiz ) {…return (*env)->NewStringUTF(env,"Hello from JNI ! Compiled with ABI" ABI "."); }這里只是實現了Java_com_example_hellojni_HelloJni_stringFromJNI方法,而 Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI方法并沒有實現,因為在HelloJni.java中只調用了stringFromJNI()方法,所以unimplementedStringFromJNI()方法沒有實現也沒關系,不過建議最好還是把所有java中定義的本地方法都實現了。
?
Java_com_example_hellojni_HelloJni_stringFromJNI()函數只是簡單的返回了一個內容為 " Hello from JNI !? Compiled with ABI " ABI "." 的jstring對象(對應于java中的String對象)。
?
6.3? Android.mk文件
LOCAL_PATH :=$(call my-dir)include$(CLEAR_VARS)LOCAL_MODULE := hello-jni LOCAL_SRC_FILES :=hello-jni.cinclude$(BUILD_SHARED_LIBRARY)(1)??LOCAL_PATH:= $(call my-dir)
一個Android.mk 文件首先必須定義好LOCAL_PATH變量。它用于在開發樹中查找源文件。在這個例子中,宏函數’my-dir’, 由編譯系統提供,用于返回當前路徑(即包含Android.mk file文件的目錄)。
?
(2)??include$(CLEAR_VARS)
CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE為你清除許多LOCAL_XXX變量(例如 LOCAL_MODULE, LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES, 等等...),
除LOCAL_PATH 。這是必要的,因為所有的編譯控制文件都在同一個GNU MAKE執行環境中,所有的變量都是全局的。
?
(3)??LOCAL_MODULE??? := hello-jni
編譯的目標對象,LOCAL_MODULE變量必須定義,以標識你在Android.mk文件中描述的每個模塊。名稱必須是唯一的,而且不包含任何空格。
?
注意:編譯系統會自動產生合適的前綴和后綴,換句話說,一個被命名為'hello-jni'的共享庫模塊,將會生成'libhello-jni.so'文件。
?
重要注意事項:
如果你把庫命名為‘libhello-jni’,編譯系統將不會添加任何的lib前綴,也會生成 'libhello-jni.so',這是為了支持來源于Android平臺的源代碼的Android.mk文件,如果你確實需要這么做的話。
?
(4)??LOCAL_SRC_FILES:= hello-jni.c
LOCAL_SRC_FILES變量必須包含將要編譯打包進模塊中的C或C++源代碼文件。注意,你不用在這里列出頭文件和包含文件,因為編譯系統將會自動為你找出依賴型的文件;僅僅列出直接傳遞給編譯器的源代碼文件就好。
?
注意,默認的C++源碼文件的擴展名是’.cpp’. 指定一個不同的擴展名也是可能的,只要定義LOCAL_DEFAULT_CPP_EXTENSION變量,不要忘記開始的小圓點(也就是’.cxx’,而不是’cxx’)
?
(5)??include$(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變量,指向一個GNU Makefile腳本,負責收集自從上次調用'include $(CLEAR_VARS)'以來,定義在LOCAL_XXX變量中的所有信息,并且決定編譯什么,如何正確地去做。還有 BUILD_STATIC_LIBRARY變量表示生成靜態庫:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可執行文件。
?
?
6.4? Application.mk文件
APP_ABI := allApplication.mk用來描述你的工程下的native模塊, 一般放在$PROJECT/jni/Application.mk, 其中$PROJECT表示你的工程目錄,這樣就可以被ndk-build腳本文件找到
?
APP_ABI
?
在默認情況下,NDK會使用'armeabi' ABI 來生成二進制機器碼,這是基于ARMv5TE的浮點運算CPU,這可以通過使用此變量來選項不同的ABI(Application Binary Interface).
?
例如:支持基于armv7 FPU指令集的設備:
APP_ABI :=armeabi-v7a
支持IA-32指令集:
APP_ABI := x86
同時支持三種:
APP_ABI := armeabiarmeabi-v7a x86
從NDK-r7版本后,同時支持三種還可以這樣寫:
APP_ABI := all
有關ABI更詳細的內容請參考文檔:docs/CPU-ARCH-ABIS.html。
?
6.5? 編譯生成.so共享庫文件
進入工程hello-jni目錄下,用ndk-build編譯,生成libhello-jni.so,如下圖:
圖6
?
7.?????生成apk
打開Eclipse導入HelloJni工程,重新編譯生成apk,libhello-jni.so共享庫會一起打包在apk文件內,在模擬器中看看運行結果:
圖7
?
?
?
?
?
?
?
參考鏈接:
為何要用到NDK?
http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html
?
總結用cygwin+eclipse+NDK編譯hellojni的詳細步驟,以及所遇到問題的解決方法
http://www.verydemo.com/demo_c131_i6149.html
?
Eclipse .classpath文件淺談:
http://mx19841031.iteye.com/blog/225593
?
NDK Application.mk使用手冊
http://www.oschina.net/question/565065_93983
?
總結
以上是生活随笔為你收集整理的Android应用---基于NDK的samples例程hello-jni学习NDK开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在eclipse中配置android n
- 下一篇: 探讨如何成为技术团队管理者