Android 開發手記之NDK 編程執行個體

來源:互聯網
上載者:User

在 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 ! ”

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.