Android之NDK開發

 1、NDK產生的背景

  Android平臺從誕生起,就已經支持C、C++開發。衆所周知,Android的SDK基於Java實現,這意味着基於Android SDK進行開發的第三方應用都必須使用Java語言。但這並不等同於「第三方應用只能使用Java」。在Android SDK首次發佈時,Google就宣稱其虛擬機Dalvik支持JNI編程方式,也就是第三方應用徹底能夠經過JNI調用本身的C動態庫,即在Android平臺上,「Java+C」的編程方式是一直均可以實現的。javascript

  不過,Google也表示,使用原生SDK編程相比Dalvik虛擬機也有一些劣勢,Android SDK文檔裏,找不到任何JNI方面的幫助。即便第三方應用開發者使用JNI完成了本身的C動態連接庫(so)開發(JNI做用),可是so如何和應用程序一塊兒打包成apk併發布(NDK做用)?這裏面也存在技術障礙。好比程序更加複雜,兼容性難以保障,沒法訪問Framework API,Debug難度更大等。開發者須要自行斟酌使用。php

  因而NDK就應運而生了。NDK全稱是Native Development Kithtml

  NDK的發佈,使「Java+C」的開發方式終於轉正,成爲官方支持的開發方式。NDK將是Android平臺支持C開發的開端。java

 

2、爲何使用NDKlinux

  1.代碼的保護。因爲apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大。android

  2.能夠方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的。c++

  3.提升程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提升應用程序的執行效率。express

  4.便於移植。用C/C++寫得庫能夠方便在其餘的嵌入式平臺上再次使用。apache

 

3、NDK簡介編程

       1.NDK是一系列工具的集合

       NDK提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,並能自動將so和java應用一塊兒打包成apk。這些工具對開發者的幫助是巨大的。

NDK集成了交叉編譯器(交叉編譯器須要UNIX或LINUX系統環境,並提供了相應的mk文件隔離CPU、平臺、ABI等差別,開發人員只須要簡單修改mk文件(指出「哪些文件須要編譯」、「編譯特性要求」等),就能夠建立出so。

NDK能夠自動地將so和Java應用一塊兒打包,極大地減輕了開發人員的打包工做。

       2.NDK提供了一份穩定、功能有限的API頭文件聲明

       Google明確聲明該API是穩定的,在後續全部版本中都穩定支持當前發佈的API。從該版本的NDK中看出,這些API支持的功能很是有限,包含有:C標準庫(libc)、標準數學庫(libm)、壓縮庫(libz)、Log庫(liblog)。

 

4、NDK開發環境的搭建

1.下載安裝Android NDK

  地址:http://developer.android.com/sdk/ndk/index.html

2.下載安裝cygwin

  因爲NDK編譯代碼時必需要用到make和gcc,因此你必須先搭建一個linux環境, cygwin是一個在windows平臺上運行的unix模擬環境,它對於學習unix/linux操做環境,或者從unix到windows的應用程序移植,很是有用。經過它,你就能夠在不安裝linux的狀況下使用NDK來編譯C、C++代碼了。下載地址:http://www.cygwin.com

  1)而後雙擊運行吧,運行後你將看到安裝嚮導界面。

  2)點擊下一步,此時讓你選擇安裝方式:

  • Install from Internet:直接從Internet上下載並當即安裝(安裝完成後,下載好的安裝文件並不會被刪除,而是仍然被保留,以便下次再安裝)。
  • Download Without Installing:只是將安裝文件下載到本地,但暫時不安裝。
  • Install from Local Directory:不下載安裝文件,直接從本地某個含有安裝文件的目錄進行安裝。

  3)選擇第一項,而後點擊下一步。

  4)選擇要安裝的目錄,注意,最好不要放到有中文和空格的目錄裏,彷佛會形成安裝出問題,其它選項不用變,以後點下一步:

  5)上一步是選擇安裝cygwin的目錄,這個是選擇你下載的安裝包所在的目錄,默認是你運行setup.exe的目錄,直接點下一步就能夠:

  6)此時你共有三種鏈接方式選擇:

  • Direct Connection:直接鏈接。
  • Use IE5 Settings:使用IE的鏈接參數設置進行鏈接。
  • Use HTTP/FTP Proxy:使用HTTP或FTP代理服務器進行鏈接(須要輸入服務器地址、端口號)。

  用戶可根據本身的網絡鏈接的實情狀況進行選擇,通常正常狀況下,均選擇第一種,也就是直接鏈接方式。而後再點擊「下一步」。

  7)這是選擇要下載的站點,選擇後點下一步。

  8)此時會下載加載安裝包列表

  9)Search是能夠輸入你要下載的包的名稱,可以快速篩選出你要下載的包。那四個單選按鈕是選擇下邊樹的樣式,默認就行,不用動。View默認是Category,建議改爲full顯示所有包再查,省的一些包被隱藏掉。左下角那個複選框是是否隱藏過時包,默認打鉤,不用管它就行,下邊開始下載咱們要安裝的包吧,爲了不所有下載,這裏列出了後面開發NDK用得着的包:autoconf2.一、automake1.十、binutilsgcc-core、gcc- g++、gcc4-core、gcc4-g++、gdb、pcre、pcre-devel、gawk、make共12個包

  10)而後開始選擇安裝這些包吧,點skip,把它變成數字版本格式,要確保Bin項變成叉號,而Src項是源碼,這個就不必選了。

  11)下面測試一下cygwin是否是已經安裝好了。

      運行cygwin,在彈出的命令行窗口輸入:cygcheck -c cygwin命令,會打印出當前cygwin的版本和運行狀 態,若是status是ok的話,則cygwin運行正常。

      而後依次輸入gcc –version,g++ --version,make –version,gdb –version進行測試,若是都打印出版本信息和一些描述信息,則cygwin安裝成功!

3.配置 NDK 環境變量

  a.首先找到 cygwin 的安裝目錄,找到一個 home\< 你的用戶名 >\.bash_profile 文件,個人是:E:\cygwin\home\Administrator\.bash_profile , ( 注意:我安裝的時候個人 home 文件夾下面什麼都沒有,解決 的辦法:首先打開環境變量,把裏面的用戶變量中的 HOME 變量刪掉,在 E:\cygwin\home 文件夾下創建名爲Administrator 的文件夾(是用戶名),而後把 E:\cygwin\etc\skel\.bash_profile 拷貝到該文件夾下 ) 。

  b.打開 bash_profile 文件,添加 NDK=/cygdrive/< 你的盤符 >/<android ndk 目錄 > 例如:

      NDK=/cygdrive/e/android-ndk-r5

      export NDK

  NDK 這個名字是隨便取的,爲了方面之後使用方便,選個簡短的名字,而後保存

  c.打開 cygwin ,輸入 cd $NDK ,若是輸出上面配置的 /cygdrive/e/android-ndk-r5 信息,則代表環境變量設置成功了。

 

4.用 NDK 來編譯程序   

  a.如今咱們用安裝好的 NDK 來編譯一個簡單的程序吧,咱們選擇 ndk 自帶的例子 hello-jni ,個人位於E:\android-ndk-r5\samples\hello-jni( 根據你具體的安裝位置而定 ) ,

  b.運行 cygwin ,輸入命令 cd /cygdrive/e/android-ndk-r5/samples/hello-jni ,進入到 E:\android-ndk-r5\samples\hello-jni 目錄。

  c.輸入 $NDK/ndk-build ,執行成功後,它會自動生成一個 libs 目錄,把編譯生成的 .so 文件放在裏面。 ($NDK是調用咱們以前配置好的環境變量, ndk-build 是調用 ndk 的編譯程序 )

  d.此時去 hello-jni 的 libs 目錄下看有沒有生成的 .so 文件,若是有,你的 ndk 就運行正常啦!

 

5.在 eclipse 中集成 c/c++ 開發環境

  a.裝 Eclipse 的 C/C++ 環境插件: CDT ,這裏選擇在線安裝。  首先登陸 http://www.eclipse.org/cdt/downloads.php ,找到對應你 Eclipse 版本的 CDT 插件 的在線安裝地址。

  b.而後點 Help 菜單,找到 Install New Software 菜單

  c.點擊 Add 按鈕,把取的地址填進去,出來插件列表後,選 Select All ,而後選擇下一步便可完成安裝。

  d.安裝完成後,在 eclispe 中右擊新建一個項目,若是出現了 c/c++ 項目,則代表你的 CDT 插件安裝成功啦!

 

6.配置 C/C++ 的編譯器(直接在eclipse中就能夠編譯C++代碼,而不須要在命令行執行 ndk-build 命令)

  a.打開 eclipse ,導入ndk 自帶的hello-jni 例子,右鍵單擊項目名稱,點擊 Properties ,彈出配置界面,以後再點擊 Builders ,彈出項目的編譯工具列表,以後點擊 New,新添加一個編譯器,點擊後出現添加界面,選擇 Program ,點擊 OK。

  b.出現了添加界面,首先給編譯配置起個名字,如: C_Builder,設置 Location 爲 < 你 cygwin 安裝路徑 >\bin\bash.exe 程序,例:E:\cygwin\bin\bash.exe ,設置Working Directory爲<你 cygwin 安裝路徑 >\bin 目錄,例如: E:\cygwin\bin,設置 Arguments 爲 --login -c "cd /cygdrive/e/android-ndk-r5/samples/hello-jni && $NDK /ndk-build"

  上面的配置中 /cygdrive/e/android-ndk-r5/samples/hello-jni 是你當前要編譯的程序的目錄, $NDK 是以前配置  的 ndk 的環境變量,這兩個根據你具體的安裝目錄進行配置,其餘的不用變, Arguments 這串參數實際是  給 bash.exe 命令行程序傳參數,進入要編譯的程序目錄,而後運行 ndk-build 編譯程序

  c.接着切換到 Refresh 選項卡,給 Refresh resources upon completion 打上鉤

  d.而後切換到 Build Options 選項卡,勾選上最後三項

  e.以後點擊 Specify Resources 按鈕,選擇資源目錄,勾選你的項目目錄便可

  f.最後點擊 Finish,點擊 OK 一路把剛纔的配置都保存下來,注意:若是你配置的編譯器在其它編譯器下邊,記得必定要點 Up 按鈕,把它排到第一位,不然 C 代碼的編譯晚於Java代碼的編譯,會形成你的 C 代碼要編譯兩次才能看到最新的修改。

  g.編譯配置也配置完成啦,如今來測試一下是否能夠自動編譯呢,打開項目 jni 目錄裏的 hello-jni.c 文件把提示 Hello from JNI! 改爲其餘的文字:如: Hello , My name is alex. ,而後再模 擬器中運行你的程序,若是模擬器中顯示了你最新修改的文字,那麼 Congratulations !你已經所有配置成功啦!

 

5、開發本身的NDK程序

  入門的最好辦法就是學習Android自帶的例子, 這裏就經過學習Android的NDK自帶的demo程序:hello-jni來達到這個目的。

一、 開發環境的搭建

  1)android的NDK開發須要在linux下進行: 由於須要把C/C++編寫的代碼生成能在arm上運行的.so文件,這就須要用到交叉編譯環境,而交叉編譯須要在linux系統下才能完成。

  2)安裝android-ndk開發包,這個開發包能夠在google android 官網下載: 經過這個開發包的工具才能將android jni 的C/C++的代碼編譯成庫

  3)android應用程序開發環境: 包括eclipse、java、 android sdk、 adt等。

  如何下載和安裝android-ndk我這裏就不囉嗦了,安裝完以後,須要將android-ndk的路勁加到環境變量PATH中:

    sudo gedit /etc/environment

  在environment的PATH環境變量中添加你的android-ndk的安裝路勁,而後再讓這個更改的環境變量當即生效:

     source  /etc/environment

  通過了上述步驟,在命令行下敲:

    ndk-bulid

  彈出以下的錯誤,而不是說ndk-build not found,就說明ndk環境已經安裝成功了。

    Android NDK: Could not find application project directory !    
    Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    
    /home/braincol/workspace/android/android-ndk-r5/build/core/build-local.mk:85: *** Android NDK: Aborting    .  Stop.

 2.代碼的編寫

  1)首先是寫java代碼

  創建一個Android應用工程HelloJni,建立HelloJni.java文件:

  HelloJni.java :

複製代碼
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class HelloJni 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() );
setContentView(tv);
}


/* A native method that is implemented by the 'hello-jni' native library, which is packaged with this application. */
public native String stringFromJNI();

public native String unimplementedStringFromJNI();



/* this is used to load the 'hello-jni' library on application startup. The library has already been unpacked into

/data/data/com.example.HelloJni/lib/libhello-jni.so at installation time by the package manager. */
static {
System.loadLibrary("hello-jni");
}
}
複製代碼

  這段代碼很簡單,註釋也很清晰,這裏只提兩點::

static {
System.loadLibrary( "hello-jni" );
}

  代表程序開始運行的時候會加載hello-jni, static區聲明的代碼會先於onCreate方法執行。若是你的程序中有多個類,並且若是HelloJni這個類不是你應用程序的入口,那麼hello-jni(完整的名字是libhello-jni.so)這個庫會在第一次使用HelloJni這個類的時候加載。

public  native String stringFromJNI();
public  native String unimplementedStringFromJNI();

  能夠看到這兩個方法的聲明中有 native 關鍵字, 這個關鍵字表示這兩個方法是本地方法,也就是說這兩個方法是經過本地代碼(C/C++)實現的,在java代碼中僅僅是聲明。

  用eclipse編譯該工程,生成相應的.class文件,這步必須在下一步以前完成,由於生成.h文件須要用到相應的.class文件。

2)編寫相應的C/C++代碼

  剛開始學的時候,有個問題會讓人很困惑,相應的C/C++代碼如何編寫,函數名如何定義? 這裏講一個方法,利用javah這個工具生成相應的.h文件,而後根據這個.h文件編寫相應的C/C++代碼。

  a. 生成相應.h文件:

  就拿我這的環境來講,首先在終端下進入剛剛創建的HelloJni工程的目錄:

braincol@ubuntu:~$ cd workspace/android/NDK/hello-jni/

  ls查看工程文件  

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls
AndroidManifest.xml  assets  bin  default .properties  gen  res  src

  能夠看到目前僅僅有幾個標準的android應用程序的文件(夾)。

  首先咱們在工程目錄下創建一個jni文件夾:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ mkdir jni
braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls
AndroidManifest.xml  assets  bin  default .properties  gen  jni  res  src

  下面就能夠生成相應的.h文件了:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ javah -classpath bin -d jni com.example.hellojni.HelloJni

  -classpath bin:表示類的路勁

  -d jni: 表示生成的頭文件存放的目錄

  com.example.hellojni.HelloJni 則是完整類名

  這一步的成功要創建在已經在 bin/com/example/hellojni/  目錄下生成了 HelloJni.class的基礎之上。如今能夠看到jni目錄下多了個.h文件:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ cd jni/
braincol@ubuntu:~/workspace/android/NDK/hello-jni/jni$ ls
com_example_hellojni_HelloJni.h

  咱們來看看com_example_hellojni_HelloJni.h的內容:

  com_example_hellojni_HelloJni.h :

複製代碼
/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni

#define _Included_com_example_hellojni_HelloJni

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: com_example_hellojni_HelloJni

* Method: stringFromJNI

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI

(JNIEnv *, jobject);


/*

* Class: com_example_hellojni_HelloJni

* Method: unimplementedStringFromJNI

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI

(JNIEnv *, jobject);


#ifdef __cplusplus

}

#endif

#endif
複製代碼

  上面代碼中的JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不須要,固然寫上去也不會有錯。從上面的源碼中能夠看出這個函數名那是至關的長啊。。。。 不過仍是頗有規律的, 徹底按照:java_pacakege_class_mathod 形式來命名。

  也就是說:

  Hello.java中 stringFromJNI() 方法對應於 C/C++中的 Java_com_example_hellojni_HelloJni_stringFromJNI() 方法

  HelloJni.java中的 unimplementedStringFromJNI() 方法對應於 C/C++中的 Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI() 方法 

  注意下其中的註釋:

  Signature: ()Ljava/lang/String; 

  ()Ljava/lang/String;()表示函數的參數爲空(這裏爲空是指除了JNIEnv *, jobject 這兩個參數以外沒有其餘參數,JNIEnv*, jobject是全部jni函數必有的兩個參數,分別表示jni環境和對應的java類(或對象)自己),Ljava/lang/String; 表示函數的返回值是java的String對象。

 

b. 編寫相應的.c文件:

  hello-jni.c :

複製代碼
#include <string.h>
#include <jni.h>


/* This is a trivial JNI example where we use a native method

* to return a new VM String. See the corresponding 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 !");
}
複製代碼

  這裏只是實現了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 !" 的jstring對象(對應於java中的String對象)。hello-jni.c文件已經編寫好了,如今能夠把com_example_hellojni_HelloJni.h文件給刪了,固然留着也行,只是我仍是習慣把不須要的文件給清理乾淨了。

 

3)編譯hello-jni.c 生成相應的庫

a 編寫Android.mk文件

  在jni目錄下(即hello-jni.c 同級目錄下)新建一個Android.mk文件,Android.mk 文件是Android 的 makefile文件,內容以下:

複製代碼
# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)



include $(CLEAR_VARS)



LOCAL_MODULE := hello-jni

LOCAL_SRC_FILES := hello-jni.c



include $(BUILD_SHARED_LIBRARY)
複製代碼


  這個Androd.mk文件很短,下面咱們來逐行解釋下:

    LOCAL_PATH := $(call my-dir)

  一個Android.mk 文件首先必須定義好LOCAL_PATH變量。它用於在開發樹中查找源文件。在這個例子中,宏函數’my-dir’, 由編譯系統提供,用於返回當前路徑(即包含Android.mk file文件的目錄)。

    include $( CLEAR_VARS)

  CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE爲你清除許多LOCAL_XXX變量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...), 除LOCAL_PATH 。這是必要的,由於全部的編譯控制文件都在同一個GNU MAKE執行環境中,全部的變量都是全局的。

    LOCAL_MODULE := hello-jni

  編譯的目標對象,LOCAL_MODULE變量必須定義,以標識你在Android.mk文件中描述的每一個模塊。名稱必須是惟一的,並且不包含任何空格。

注意:編譯系統會自動產生合適的前綴和後綴,換句話說,一個被命名爲'hello-jni'的共享庫模塊,將會生成'libhello-jni.so'文件。

  重要注意事項:若是你把庫命名爲‘libhello-jni’,編譯系統將不會添加任何的lib前綴,也會生成 'libhello-jni.so',這是爲了支持來源於Android平臺的源代碼的Android.mk文件,若是你確實須要這麼作的話。

    LOCAL_SRC_FILES := hello-jni.c

  LOCAL_SRC_FILES變量必須包含將要編譯打包進模塊中的C或C++源代碼文件。注意,你不用在這裏列出頭文件和包含文件,由於編譯系統將會自動爲你找出依賴型的文件;僅僅列出直接傳遞給編譯器的源代碼文件就好。

  注意,默認的C++源碼文件的擴展名是’.cpp’. 指定一個不一樣的擴展名也是可能的,只要定義LOCAL_DEFAULT_CPP_EXTENSION變量,不要忘記開始的小圓點(也就是’.cxx’,而不是’cxx’)

    include $(BUILD_SHARED_LIBRARY)

  BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變量,指向一個GNU Makefile腳本,負責收集自從上次調用'include $(CLEAR_VARS)'以來,定義在LOCAL_XXX變量中的全部信息,而且決定編譯什麼,如何正確地去作。還有 BUILD_STATIC_LIBRARY變量表示生成靜態庫:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可執行文件。

 

b. 生成.so共享庫文件

  Andro文件已經編寫好了,如今能夠用android NDK開發包中的 ndk-build腳本生成對應的.so共享庫了,方法以下:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni/jni$ cd .. 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls 
    AndroidManifest.xml  assets  bin  default.properties  gen  jni  libs  obj  res  src 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ndk-build 
    Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver 
    Gdbsetup       : libs/armeabi/gdb.setup 
    Install        : libhello-jni.so => libs/armeabi/libhello-jni.so 

  能夠看到已經正確的生成了libhello-jni.so共享庫了, 咱們去 libs/armeabi/ 目錄下看看:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ cd libs/ 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs$ ls 
    armeabi 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs$ cd armeabi/ 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs/armeabi$ ls 
    gdbserver  gdb.setup  libhello-jni.so

4)在eclipse從新編譯HelloJni工程,生成apk

  eclipse中刷新下HelloJni工程,從新編譯生成apk,libhello-jni.so共享庫會一塊兒打包在apk文件內。在模擬器中看看運行結果。

 

參考資料:

http://blog.csdn.net/hhao137/article/details/4304664

http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html

http://yueguc.iteye.com/blog/946724

http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html

【以上轉自】http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html

 

 

 

【如下轉自】http://blog.csdn.net/redoffice/article/details/6654714

Android NDK開發入門實例

        寫這個,目的就是記錄一下我本身的NDK是怎麼入門的。便於之後查看,而不會忘了又用搜索引擎一頓亂搜。而後但願可以幫助剛學的人入門。先轉一段別人說的話:

「NDK全稱:Native Development Kit。

一、NDK是一系列工具的集合。

* NDK提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,並能自動將so和java應用一塊兒打包成apk。這些工具對開發者的幫助是巨大的。

* NDK集成了交叉編譯器,並提供了相應的mk文件隔離CPU、平臺、ABI等差別,開發人員只須要簡單修改mk文件(指出「哪些文件須要編譯」、「編譯特性要求」等),就能夠建立出so。

* NDK能夠自動地將so和Java應用一塊兒打包,極大地減輕了開發人員的打包工做。

二、NDK提供了一份穩定、功能有限的API頭文件聲明。

Google明確聲明該API是穩定的,在後續全部版本中都穩定支持當前發佈的API。從該版本的NDK中看出,這些API支持的功能很是有限,包含有:C標準庫(libc)、標準數學庫(libm)、壓縮庫(libz)、Log庫(liblog)。」

在深刻理解以前,暫且就把NDK當成是一種工具,這種工具使得JAVA可以使用C/C++編譯出的so包。並將此包一塊兒打入apk包中。

下面開始正式入門:

1、開發環境搭建(以Windows平臺下爲例,Linux平臺下相似)

1.      下載NDK壓縮包,至於從哪裏下載藉助搜索引擎吧。

2.       解壓NDK壓縮包,配置環境變量。將解壓的地址寫入環境變量PATH中【本人下載了android-ndk-r8d並解壓到D:\software\adt\android-ndk-r8d, 在PATH路徑中增長「D:\software\adt\android-ndk-r8d」】

3.       在命令提示符下輸入ndk-build若是彈出以下的錯誤,而不是說ndk-build not found,就說明ndk環境已經安裝成功了。特別提示一下,搜索引擎中會告訴一些早期的NDK版本的使用,是在命令提示符下輸入build/host-setup.sh;可是NDK通過更新,這個文件已經沒有了。只須要輸入ndk-build就能夠了。

Android NDK: Could not find application project directory !    
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    
/home/braincol/workspace/android/android-ndk-r5/build/core/build-local.mk:85: *** Android NDK: Aborting    .  Stop.

4.       cygwin的安裝,至於如何安裝,從哪裏安裝也去借助搜索引擎吧。在Cygwin安裝過程當中,可能比較慢,必須安裝的項目有make和gcc;建議安裝下面幾個包:autoconf2.1 ,automake1.10 ,binutils ,gcc-core ,gcc4-core ,gdb ,pcre ,pcre-devel,都安裝最新版本。安裝完畢後,運行Cygwin,輸入"make -v"和"gcc -v"來檢測是否安裝成功;make版本要在3.81以上。【本人安裝中止在最後階段,問題不明,待解】

5.       以上任務結束後,就表示基本的開發環境搭建完成了。

2、編寫JAVA代碼

1.       創建一個android的工程TestNDK,建立TestNDK.java文件。

TestNDK.java:

[java]  view plain copy
  1. package com.blueeagle.example;  
      
    import android.app.Activity;  
    import android.widget.TextView;  
    import android.os.Bundle;  
      
      
    public class TestNDK extends Activity  
    {  
        @Override  
        public void onCreate(Bundle savedInstanceState)  
        {  
            super.onCreate(savedInstanceState);  
      
            TextView  myTextView = new TextView(this);  
            myTextView.setText( stringTestNdk() );  
            setContentView(myTextView);  
        }  
      
        public native String  stringTestNdk ();  
        public native String  stringTestNdk2 ();  
      
        static {  
            System.loadLibrary("testNDK");  
        }  
    } 

     

2.       必要的一些說明

[java]  view plain copy
 
static   
  
{  
System.loadLibrary("testNDK ");  
}  

 

代表程序開始運行的時候會加載testNDK, static區聲明的代碼會先於onCreate方法執行。若是程序中有多個類,並且若是TestNDK這個類不是你應用程序的入口,那麼testNDK(完整的名字是lib testNDK.so)這個庫會在第一次使用TestNDK這個類的時候加載。

[java]  view plain copy
 
public native String stringTestNdk ();  
public native String stringTestNdk 2();  

 

能夠看到這兩個方法的聲明中有 native 關鍵字, 這個關鍵字表示這兩個方法是本地方法,也就是說這兩個方法是經過本地代碼(C/C++)實現的,在java代碼中僅僅是聲明。

用eclipse編譯該工程,生成相應的.class文件,這步必須在下一步以前完成,由於生成.h文件須要用到相應的.class文件。暫時不考慮報錯信息。

3. 生成.h文件(若是發生錯誤,見javah 生成.h文件時出現的錯誤

在C/C++文件編寫以前,須要利用javah這個工具生成相應的.h文件,而後根據這個.h文件編寫相應的C/C++代碼。

進入到剛纔創建的testNDK工程目錄中,查看工程文件:AndroidManifest.xml  assets  bin  default.properties  gen  res  src並新建一個ndk的文件夾後就能夠進行.h文件的生成了。

       在工程目錄下執行: javah -classpath bin -d ndk com.blueeagle.example.TestNDK

這裏-classpath表示類的路徑;-d ndk 表示生成的.h文件存放的目錄;com.blueeagle.example.TestNDK則是完整的類名。

       如今能夠再ndk目錄下看到多了一個.h文件:com_blueeagle_example_testNDK.h;打開後,能夠看到.h的內容:

com_blueeagle_example_testNDK.h:

[cpp]  view plain copy
 
#include <jni.h>  
  
#ifndef _Included_com_blueeagle_example_testNDK  
#define _Included_com_blueeagle_example_testNDK  
#ifdef __cplusplus  
extern "C" {  
#endif  
/* 
 * Class:     com_blueeagle_example_testNDK 
 * Method:    stringTestNdk 
 * Signature: ()Ljava/lang/String; 
 */  
JNIEXPORT jstring JNICALL Java_ com_blueeagle_example_testNDK_stringTestNdk  
  (JNIEnv *, jobject);  
  
/* 
 * Class:     com_blueeagle_example_testNDK 
 * Method:    stringTestNdk2 
 * Signature: ()Ljava/lang/String; 
 */  
JNIEXPORT jstring JNICALL Java_ com_blueeagle_example_testNDK_stringTestNdk2  
  (JNIEnv *, jobject);  
  
#ifdef __cplusplus  
}  
#endif  
#endif  

上面代碼中的JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不須要,固然寫上去也不會有錯。

函數名比較長可是徹底按照:java_pacakege_class_mathod 形式來命名。

也就是說:

TestNDK.java中stringTestNdk() 方法對應於 C/C++中的 Java_com_blueeagle_example_testNDK_ stringTestNdk() 方法

TestNDK.java中stringTestNdk2() 方法對應於 C/C++中的 Java_com_blueeagle_example_testNDK_ stringTestNdk2() 方法

注意下其中的註釋:

Signature: ()Ljava/lang/String;

()Ljava/lang/String;

()表示函數的參數爲空(這裏爲空是指除了JNIEnv *, jobject 這兩個參數以外沒有其餘參數,JNIEnv*, jobject是全部jni函數必有的兩個參數,分別表示jni環境和對應的java類(或對象)自己),

Ljava/lang/String; 表示函數的返回值是java的String對象。

3、編寫C/C++文件

testNDK.c:

[cpp]  view plain copy
 
#include <string.h>  
#include <jni.h>  
  
jstring  
Java_com_blueeagle_example_testNDK_stringTestNdk( JNIEnv* env,  
                                                  jobject thiz )  
{  
    return (*env)->NewStringUTF(env, "Hello Test NDK !");  
}  

 

這裏只是實現了Java_com_blueeagle_example_testNDK_stringTestNdk方法,而Java_com_blueeagle_example_testNDK_stringTestNdk2 方法並無實現,由於在testNDK.java中只調用了stringTestNdk ()方法,因此stringTestNdk 2()方法沒有實現也不要緊,不過建議最好仍是把全部java中定義的本地方法都實現了。

Java_com_blueeagle_example_testNDK_stringTestNdk 函數只是簡單的返回了一個內容爲 "Hello Test NDK !" 的jstring對象(對應於java中的String對象)。

testNDK.c文件就已經編寫好了,這時的.h文件已經沒有用了。

 

4、編譯生成相應的庫

1     首先須要編寫Android.mk文件

在testNDK.c的同級目錄下新建一個Android.mk的文件

[javascript]  view plain copy
LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)  
LOCAL_MODULE    := testNDK  
LOCAL_SRC_FILES := testNDK.c   
include $(BUILD_SHARED_LIBRARY)  

 

這個Androd.mk文件很短,下面咱們來逐行解釋下:

LOCAL_PATH := $(call my-dir)

一個Android.mk 文件首先必須定義好LOCAL_PATH變量。它用於在開發樹中查找源文件。在這個例子中,宏函數’my-dir’, 由編譯系統提供,用於返回當前路徑(即包含Android.mk file文件的目錄)。

include $( CLEAR_VARS)

CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE爲你清除許多LOCAL_XXX變量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),
除LOCAL_PATH 。這是必要的,由於全部的編譯控制文件都在同一個GNU MAKE執行環境中,全部的變量都是全局的。

LOCAL_MODULE := testNDK

編譯的目標對象,LOCAL_MODULE變量必須定義,以標識你在Android.mk文件中描述的每一個模塊。名稱必須是惟一的,並且不包含任何空格。

注意:編譯系統會自動產生合適的前綴和後綴,換句話說,一個被命名爲'hello-jni'的共享庫模塊,將會生成'libhello-jni.so'文件。

重要注意事項:

若是你把庫命名爲‘libtestNDK’,編譯系統將不會添加任何的lib前綴,也會生成libfoo.so,這是爲了支持來源於Android平臺的源代碼的Android.mk文件,若是你確實須要這麼作的話。

LOCAL_SRC_FILES := testNDK.c

LOCAL_SRC_FILES變量必須包含將要編譯打包進模塊中的C或C++源代碼文件。注意,你不用在這裏列出頭文件和包含文件,由於編譯系統將會自動爲你找出依賴型的文件;僅僅列出直接傳遞給編譯器的源代碼文件就好。

注意,默認的C++源碼文件的擴展名是’.cpp’. 指定一個不一樣的擴展名也是可能的,只要定義LOCAL_DEFAULT_CPP_EXTENSION變量,不要忘記開始的小圓點(也就是’.cxx’,而不是’cxx’)

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變量,指向一個GNU Makefile腳本,負責收集自從上次調用'include $(CLEAR_VARS)'以來,定義在LOCAL_XXX變量中的全部信息,而且決定編譯什麼,如何正確地去作。還有 BUILD_STATIC_LIBRARY變量表示生成靜態庫:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可執行文件。

2         其次進行編譯

進入到工程根目錄,輸入ndk-build便可生成相應的庫libs/armeabi/testNDK.so

5、在eclipse從新編譯HelloJni工程,生成apk

從新編譯testNDK工程,將so包打入工程apk包,便可看到結果。在模擬器上顯示Hello Test NDK!

從新編譯:修改HelloJni.c中的顯示字符串 --> 保存 --> 刪除項目 --> 在命令行從新生成動態庫「ndk-build」 --> 從新導入項目 --> 運行項目獲得顯示新的修改字符串。(本人顯示成功,方法如上,不過這種須要刪除在從新導入項目、而且在命令行從新生成庫的方法顯然麻煩又低效,在eclipse中集成C++編譯器尚未嘗試)

相關文章
相關標籤/搜索