【轉】 Android開發手記一 NDK編程實例


Android 開發手記一html

---- NDK 編程實例java

 

        Android 上,應用程序的開發,大部分基於 Java 語言來實現。要使用 c 或是 c++ 的程序或庫,就須要使用 NDK來實現。 NDK  Native Development Kit 的簡稱。它是一個工具集,集成了 Android 的交叉編譯環境,並提供了一套比較方便的 Makefile ,能夠幫助開發者快速開發 C 或是 C++ 的動態庫,並自動的將 so  java 程序打包成 apk ,在Android 上運行。android

       好,閒話少說,咱們以一個簡單的實例,來說解 NDK 的應用。c++

 

        開發環境的搭建編程

       這一步雖然沒什麼技術含量,可是對於初學者,有一個很好的入門指導,仍是頗有幫助的。windows

1.1   Android SDK 的搭建app

       首先,要進行 Android 程序的開發, Android  SDK 是必需要安裝的。固然, Java 環境也必不可少。咱們先要安裝JDK  Eclipse ,這個能夠選比較新的版本,由於 Android 新的 SDK 已經不支持舊版本了。eclipse

       1.1.1 JDK 能夠用 V5  V6 版本,下載地址 http://java.sun.com/javase/downloads/index.jspjsp

       1.1.2 Eclipse 能夠用版本 version 3.4 or 3.5 ,下載地址 http://www.eclipse.org/downloads/ . 固然,若你須要其餘的Java 開發環境,能夠不用 Eclipse ,不過這樣也就用不了 ADT(Android Development Tools) 插件了。推薦仍是用 Eclipse來進行開發比較好,畢竟比較權威和方便麼。ide

       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  

相關文章
相關標籤/搜索