http://blog.csdn.net/l____j/archive/2010/08/04/5787759.aspx
在 Android 上,應用程序的開發,大部分基於 Java 語言來實現。要使用 c 或是 c++ 的程序或庫,就需要使用 NDK 來實現。 NDK 是 Native Development Kit 的簡稱。它是一個工具集,集成了 Android 的交叉編譯環境,並提供了一套比較方便的 Makefile ,可以幫助開發者快速開發 C 或是 C++ 的動態庫,並自動的將 so 和 java 程序打包成 apk ,在 Android 上運行。
好,閒話少說,我們以一個簡單的實例,來講解 NDK 的應用。
一 開發環境的搭建
這一步雖然沒什麼技術含量,但是對於初學者,有一個很好的入門指導,還是很有幫助的。
1.1 Android SDK 的搭建
首先,要進行 Android 程序的開發, Android 的 SDK 是必須要安裝的。當然, Java 環境也必不可少。我們先要安裝 JDK 和 Eclipse ,這個可以選比較新的版本,因爲 Android 新的 SDK 已經不支持舊版本了。
1.1.1 JDK 可以用 V5 或 V6 版本,下載地址 http://java.sun.com/javase/downloads/index.jsp
1.1.2 Eclipse 可以用版本 version 3.4 or 3.5 ,下載地址 http://www.eclipse.org/downloads/ . 當然,若你需要其他的 Java 開發環境,可以不用 Eclipse ,不過這樣也就用不了 ADT(Android Development Tools) 插件了。推薦還是用 Eclipse 來進行開發比較好,畢竟比較權威和方便麼。
1.1.3
安裝
SDK
Android SDK 下載地址爲 http://androidappdocs.appspot.com/sdk/tools-notes.html
1.1.4
爲
Eclips
安裝插件
ADT
。在
Eclipse
中,填加更新站點
https://dl-ssl.google.com/android/eclipse/
,
然後選擇安裝
ADT.
1.1.5
接下來,我們選擇
Android
平臺和組件。若是在
window
系統下,運行
SDK Setup.exe
;若是在
Linux
系統下,運行
tools
目錄下的
android
程序,就可以選擇需要的
Android Platform
和組件。
完成以上工作後,就可以進行 Android 應用程序的開發了。可以用 Eclipse 創建一個 Android 工程,比較簡單的 Hello Android ,然後在模擬器下運行。具體的操作可以參看 Android 開發網站的說明,上面有詳細的步驟。
1.2 Android NDK 的搭建
上面我們搭建好了
SDK
的環境,可以開發
Java
應用程序了。要開發
C
的程序,還得搭建
NDK
環境。
NDK
給我們提供了以下內容:
libc
(C library) headers
libm
(math library) headers
JNI
interface headers
bz
(Zlib compression) headers
blog
(Android logging) header
A
Minimal set of headers for C++ support
1.2.1
NDK
的安裝
下載
NDK
安裝包,下載地址
http://androidappdocs.appspot.com/sdk/ndk/index.html
,下載後解壓即可使用。
1.2.2
若在
Linux
開發環境下那麼,這樣就可以使用了。若是在
window
環境下,還需要安裝
cygwin
。
cygwin
下載地址:
http://www.cygwin.com/
這樣,
NDK
的環境也搭建好了。下面我們來進行實戰演習。
二
NDK
開發實例
關於
NDK
的使用,首先需要了解一個概念:
JNI
。什麼是
JNI
?
2.1 Hello-jni
這個是
NDK
自帶的例子程序,安裝官方網站的說明,一步步來,應該沒有什麼問題,這裏就不細說了。
2.2 My God I did it
學習的第一步,就是模仿。我們依照上面
Hello-jni
的例子,在創建自己的
NDK
程序。在此過程中,對相關的內容和概念進行分析和說明。
首先,創建自己的
NDK
工程。我們在
ndk
的
sample
目錄下創建自己的工程
myjni
,然後在這個文件夾子下,創建兩個目錄
jni
和
src
,
jni
用來放我們的
c
文件,
src
是調用的
c
庫
java
接口文件。創建好目錄,接着創建文件
jni/myjni.c
,該文件比較簡單,就是輸出一個字符串,內容如下
|
這個程序,唯一和
hello-jni
不同的就是引用了
<android/log.h>
這個頭文件。在該頭文件中,聲明瞭函數
__android_log_print(),
可以根據不同的
log
級別,輸出
log
,方便代碼的調試。在
NDK
中,
printf()
沒法輸出,所以我們需要藉助
log
庫來將我們
c
代碼庫中需要輸出的內容,通過
java
控制檯輸出。調用函數
__android_log_print(),
就可以在
Eclipse
中,查看
LogCat
來查看相關的輸出信息了。
注意:
在
c
文件中,函數名這樣定義:
Java_com_jpf_myjni_MyJNI_stringFromJNI
,有什麼講究麼?這個是
JNI
的標準,定義需要按照如下格式:
Java
_packagename
_classname
_methodname
,
例如:
Java
_com_jpf_myjni
_MyJNI
_stringFromJNI
接着創建文件
jni/Android.mk.
這個文件是我們本地
c
代碼的
Makefile
。文件內容如下:
|
分別對上述
Makefile
的語句進行說明。
LOCAL_PATH
:= $(call my-dir)
這句用來指定編譯的路徑。通過調用宏
my-dir
,獲取到當前工作的路徑。
include
$(CLEAR_VARS) CLEAR_VARS
這個變量是編譯系統提供的,用來指明一個
GNU makefile
文件,添加這句,主要的目的是清理所有的
LOCAL_XXX.
,比如
LOCAL_MODULE
,
LOCAL_LDLIBS
。在每個新模塊的開始處,需要添加這句。
LOCAL_MODULE
:= myjni
這句定義了模塊名稱,將來編譯的庫就以此命名。若果編譯的是動態庫,那麼庫名就是
libmyjni.so.
需要注意的是,如果你定義
module
爲
libmyjni
,那麼系統在生成動態庫的時候,就不要再爲你添加
lib
的前綴了,生成德動態庫名字還是
libmyjni.so.
LOCAL_LDLIBS
+= -llog
這句指定了需要另外鏈接的庫。我們在代碼中,用到了
log
庫,所以這裏加上這句。
include
$(BUILD_SHARED_LIBRARY)
這句說明將來生產的庫是共享庫,及動態鏈接庫。若需要生產靜態庫,可以這樣寫:
include $(BUILD_STATIC_LIBRARY)
。
寫完了
c
文件和
Makefile
文件,是否可以編譯了呢?我們試一下。在
cygwin
中,進入工程目錄,運行
ndk-build
,得到下面的結果:
|
看到這個錯誤的意思是,缺少
manifest
文件。老版本的
NDk
,工程中有一個
apps
,裏面包含了應用的程序文件和
Application.mk
。現在的版本,不需要我們自己編寫
Application.mk,
,不過仍需要工程相關的配置信息。那麼如何做到呢?需要手工去寫
manifest
文件麼?不需要。我們只需要在
Eclipse
中,創建工程就可以了,這些配置文件會自動生成。
前面講過,在工程的
src
夾子下用來放置
Java
文件。我們打開
Eclipse
,然後新建一個
Android
工程,工程名就叫
MyJNI
,工程路徑選擇我們創建的
NDK
的路徑。這裏需要注意的是,工程名,包名等,需要和上面的
c
文件中的保持一致。
(Java
_com_jpf_myjni
_MyJNI
_stringFromJNI)
工程建立好後,編輯
src/com/jpf/myjni/MyJNI.java
文件,內容如下:
package com.jpf.myjni;
import android.app.Activity; import android.widget.TextView; import android.os.Bundle;
public class MyJNI 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() ); System. out .println( "Here we go ..." ); setContentView(tv); System. out .println( "Done!" ); }
public native String stringFromJNI(); static { System.loadLibrary ( "myjni" ); } }
|
需要說明的幾點:
public
native String
stringFromJNI();
這句申明,帶有
native
關鍵字,說明該方法是本地方法。
System.loadLibrary
(
"myjni"
);
這句就是用來加載我們的
c
動態庫的。上面聲明的方法,具體實現,就在我們加載的庫中。
建立好工程,再次編譯,在
cygwin
中運行
ndk-build
,結果
OK
。
|
我們看到,需要的共享庫已經生成,並且安裝好了。下面就可以生成
apk
了。
在
Cygwin
中進行工程的
build
,編譯後,在工程的
bin
目錄下,會看到我們的
apk
包。
好,我們試試看,能否正常運行。在
Eclipse
選擇執行方式爲
Android
Application
,點擊
run
,以下
console
的輸出:
|
上面的 warning ,是我們沒有指定 API 的版本號。如下指定一下就沒有這個 warning 了。
下圖爲執行的效果:
下圖是我們查看 LogCat 的輸出:
可以看到我們的輸出 MYJNI : MyJNI is called !
2.3 Study Hard
有了上面的基礎,我們就可以用
NDK
來進行項目開發了。
我們經常會遇到這樣的問題,就是將一些現有的,成熟的
C
庫移植到
Android
平臺上。通過上面我們的介紹,我們已經知道,我們需要用
JNI
來對現有的
C
庫包裝一下,然後提供
Java
接口,供上層調用。
首先的問題,就是
C
庫的編譯和測試。其實
Android
底層用的是
Linux
的內核,所以,和其他
Linux
程序開發一樣,無法使進行交叉編譯。不過,
Android
有些特殊的地方,我們需要注意。下面就以一個很簡單的例子,講講如何應用
NDK
,做一個
C
的應用終端測試程序。
首先,創建
study-hadr/study-hard.c
文件,程序非常簡單,就是
Hello World
的
c
程序。
|
別看程序很簡單,不過這個程序的編譯可不簡單。
若是在
Linux
下,只需要執行:
gcc
–o study-hard study-hard.c
就可以生成應用程序
study-hard
了。
在
Android
下就不是這麼簡單了。在
Window
環境開發環境下,用到的交叉工具鏈,目錄是
/android-ndk-r4/build/prebuilt/windows/arm-eabi-4.4.0
。
在這個目錄的
bin
路徑下,你會看到
arm-eabi
爲前綴的諸多工具,這些就是
Android
用的編譯工具。那麼
c
庫和
c
頭文件又在哪裏呢?對於
Android
,不同的
Platform
,有不同的庫和頭文件,需要我們自己選擇。比如,現在我們要用
Platform5
,那麼
C
頭文件的路徑爲:
/android-ndk-r4/build/platforms/android-5/arch-arm/usr/include
C
庫的路徑爲:
/android-ndk-r4/build/platforms/android-5/arch-arm/usr/lib
好了,我們知道了
C
的編譯工具鏈,知道了
C
庫路徑和
C
頭文件路徑,應該可以編譯了。寫個簡單的
Makefile
,試一下,結果出錯了。
crt0.o
沒有找到。
這個錯誤很糟糕,指出在鏈接的時候,找不到 crt0.o 。我們在 Makefile 中添加如下幾句:
LDFLAGS += -nostdlib
-nostdlib 表示不連接系統標準啓動文件和標準庫文件 . 只把指定的文件傳遞給連接器。
此時編譯,結果爲:
錯誤指出,在鏈接的時候,找不到 puts ,這個函數是 c 庫中的,我們添加如下語句再次嘗試:
LDFLAGS += -lc
我們修改鏈接選項,增加對 dl 庫的鏈接, 再次嘗試:
LDFLAGS += -lc –ldl
這次生成了可執行文件,不過還是有 warning ,在生成的可執行文件中,沒有找到入口 _start 。這個問題也比較奇怪。我們查看下生成的可執行文件 :
readelf –a study-hard
發現生成的可執行文件,真的沒有入口函數。這是爲什麼呢?
在 Linux 下,用 -v 選項跟蹤下 gcc 編譯 hello world 程序的過程。會發現,在鏈接的過程中,除了 hello.o, 還會鏈接 crt1.o, crtn.o 等文件,正是這些文件,在生成可執行程序的過程中,組成了 elf 文件中程序入口和程序退出等相關的處理部分。
查看我們指定的 C 庫:
會發現, C 庫下有 crt 打頭的三個 .o 文件。我們修改 Makefile ,鏈接 crtbegin 和 crtend 文件:
EXTRA_OBJS := $(PATH_PREFIX)/lib/crtbegin_dynamic.o $(PATH_PREFIX)/lib/crtend_android.o … … $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(EXTRA_OBJS) $(LDFLAGS) |
再次編譯,結果如下,此次終於編譯成功了。
我們將編譯好的程序放到 Android 上運行下看看效果。
顯示程序沒有找到。怎麼回事呢?繼續研究下 AndroidNDK 相關文檔。我們還需要修改 Makefile 的一個地方:
LDFALGS += -Bdynamic -Wl,-dynamic-linker,/system/bin/linker
指定鏈接動態庫,動態連接器爲 /system/bin/linker
編譯後,再次運行,終於看到了 “Study hard ! ”