Android NDK 編程實例

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 ,該文件比較簡單,就是輸出一個字符串,內容如下

#include <string.h>

#include <stdio.h>

#include <jni.h>

 

#include <android/log.h>

#define LOG_TAG "MYJNI"

 

#define LOGI(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

 

static char s_string[] = "My god, I did it!";

 

jstring

Java_com_jpf_myjni_MyJNI_stringFromJNI( JNIEnv* env,

                                        jobject thiz )

{

       LOGI("MyJNI is called!");

       return (*env)->NewStringUTF(env, s_string);

}

       這個程序,唯一和 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 。文件內容如下:

LOCAL_PATH := $(call my-dir)

 

include $(CLEAR_VARS)

 

LOCAL_MODULE := myjni

LOCAL_SRC_FILES := myjni.c

 

LOCAL_LDLIBS += -llog

 

include $(BUILD_SHARED_LIBRARY)

       分別對上述 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 ,得到下面的結果:

Administrator@lenovo-0e47e162 /android/android-ndk-r4/samples/myndk

$ ndk-build

Android NDK: Could not find application's manifest from current directory.

Android NDK: Please ensure that you are inside the project's directory !

/android/android-ndk-r4/build/core/build-local.mk:74: *** Android NDK: Aborting

   .  Stop.

       看到這個錯誤的意思是,缺少 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

Administrator@lenovo-0e47e162 /android/android-ndk-r4/samples/myndk

$ ndk-build

Compile thumb  : myjni <= /android/android-ndk-r4/samples/myndk/jni/myjni.c

SharedLibrary  : libmyjni.so

Install        : libmyjni.so => /android/android-ndk-r4/samples/myndk/libs/armea

bi

       我們看到,需要的共享庫已經生成,並且安裝好了。下面就可以生成 apk 了。

       Cygwin 中進行工程的 build ,編譯後,在工程的 bin 目錄下,會看到我們的 apk 包。



       好,我們試試看,能否正常運行。在 Eclipse 選擇執行方式爲 Android Application ,點擊 run ,以下 console 的輸出:

[2010-07-07 14:26:18 - MyJNI] ------------------------------

[2010-07-07 14:26:18 - MyJNI] Android Launch!

[2010-07-07 14:26:18 - MyJNI] adb is running normally.

[2010-07-07 14:26:18 - MyJNI] Performing com.jpf.myjni.MyJNI activity launch

[2010-07-07 14:26:18 - MyJNI] Automatic Target Mode: using existing emulator 'emulator-5554' running compatible AVD 'android21'

[2010-07-07 14:26:18 - MyJNI] WARNING: Application does not specify an API level requirement!

[2010-07-07 14:26:18 - MyJNI] Device API version is 7 (Android 2.1-update1)

[2010-07-07 14:26:18 - MyJNI] Uploading MyJNI.apk onto device 'emulator-5554'

[2010-07-07 14:26:18 - MyJNI] Installing MyJNI.apk...

[2010-07-07 14:26:24 - MyJNI] Success!

[2010-07-07 14:26:25 - MyJNI] Starting activity com.jpf.myjni.MyJNI on device

[2010-07-07 14:26:29 - MyJNI] ActivityManager: Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.jpf.myjni/.MyJNI }

       上面的 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 程序。

#include <string.h>

#include <stdio.h>

 

static char s_string[] = "Study hard!";

 

int main()

{

       printf("%s/n", s_string);

       return 0;

}

       別看程序很簡單,不過這個程序的編譯可不簡單。

       若是在 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章