1. 程式人生 > >一步一步學習androidNDK程式設計(hello world)

一步一步學習androidNDK程式設計(hello world)

         上一篇部落格,已經搭建好了windows下的linux環境(cygwine),這次我們試著寫一個hello world。首先需要去android的官網下載android-ndk壓縮包,之後解壓,進入解壓後的目錄,我們發現有一個ndk-build的指令碼檔案,這個指令碼檔案就是我們用交叉編譯的檔案。我們通過  "./ndk-build"  來執行該命令,如下圖:


        因為目前我們沒有編譯任何c程式碼,所以會提示"could not find application project directory"這樣的錯誤,這說明我們的ndk的環境,已經是ok的。但是如果我們每次執行該命令"ndk-build",難道都必須進入ndk的解壓縮目錄下來執行嗎?答案是"no",我們可以配置ndk的環境變數,就像配置java環境變數一樣。

        我們開啟cygwine的安裝目錄,我的是在c盤下的cygwine目錄下,也是預設的目錄,在該目錄下有一個etc資料夾,在etc資料夾下有一個profile的檔案,我們開啟它

將PATH原先的路徑:"PATH="/usr/local/bin:/usr/bin:" ,我們在"bin:"之後新增ndk的解壓縮的目錄,我的是在f:盤下,如下圖:紅色標註的就是我新新增的ndk的路徑:


         完成之後,儲存,然後重新開啟cygwine,在任意目錄輸入"ndk-build" ,檢視是否配置好了“ndk-build”的環境變數。

===============================================================================================

       接下來,我們來寫一個hello world,首先在eclipse下新建一個android工程,這裡我取名為"helloworld",建立好工程以後,我們需要在java程式碼中申明一個native方法,如下:

public native String helloWorldNdk();
這裡注意,括號後邊不能新增大括號,因為我們只是宣告而並未實現該方法,native關鍵字,表明該方法的實現是在c語言層面實現的。

      接下來我們呢需要在helloworld工程下建立一個jni目錄。在該目錄下新建一個hello.c檔案,在編寫hello.c檔案之前,我們需要用javah生成public native String helloWorldNdk(); 方法對應的標頭檔案,由於我們在MainActivity中宣告該方法,當我們用javac編譯的時候,由於用到了R檔案,導致生成位元組碼失敗,由於我們只是需要.h標頭檔案,所以我們可以新建一個java工程,來生成.h檔案,如下圖:


其中JavahTest.java中的內容,就是我們在Mainactivty中宣告的native方法,如下:

package com.test.example;

public class JavahTest {
	public native String helloWorldNdk();
	
}
接下來,我們生成.h檔案,首先執行javac生成.class位元組碼。

javac JavahTest.java

執行以上命令,會生成.class位元組碼,接下來生成.h檔案,如下圖:


此時,會生成一個com_test_example_JavahTest.h檔案,該檔案內容如下:

#include <jni.h>
/* Header for class com_test_example_JavahTest */

#ifndef _Included_com_test_example_JavahTest
#define _Included_com_test_example_JavahTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_test_example_JavahTest
 * Method:    helloWorldNdk
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_test_example_JavahTest_helloWorldNdk
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
其中jstring JNICALL Java_com_test_example_JavahTest_helloWorldNdk (JNIEnv *, jobject);

就是我們需要在hello.c中宣告的方法,這裡需要注意,由於我們的native方法是在MainActivity中宣告的,所以,我們需要將jstring JNICALL Java_com_test_example_JavahTest_helloWorldNdk (JNIEnv *, jobject);中的JavahTest替換成MainActivity,如下:

jstring Java_com_example_helloworld_MainActivity_helloWorldNdk(JNIEnv* env ,jobject obj)

     接下來就是完整實現hello.c的程式碼,如下:

#include<stdio.h>
#include<jni.h>

jstring Java_com_example_helloworld_MainActivity_helloWorldNdk(JNIEnv* env ,jobject obj) {

	return (*env)->NewStringUTF(env,"hello world");

} 
可以看到這裡我們返回"hello world"字串。

      記住,需要編譯該android工程中的c檔案,我們還需要編寫Android.mk檔案,同樣在jni目錄下,新建一個Android.mk檔案,內容如下:

   LOCAL_PATH := $(call my-dir)

   include $(CLEAR_VARS)

   LOCAL_MODULE    := hello
   LOCAL_SRC_FILES := hello.c

   include $(BUILD_SHARED_LIBRARY)
其中LOCAL_MODULE是我們需要編譯的模組名稱,這個名稱隨便命名的,LOCAL_SRC_FILES是我們需要編譯的原始檔

       當hello.c和Android.mk檔案都建立好了以後,我們就可以編譯該hello.c檔案了,開啟cygwine,進入該android工程,執行"ndk-build"命令,即可生成libhello.so檔案,如下圖:

        同時,我們發現在helloworld的android工程中,生成了以下檔案:


          在libs目錄下生成的libhello.so檔案就是一個可以執行的二進位制檔案。下面我們就要在java程式碼中使用該二進位制檔案。我們通過靜態程式碼塊經該二進位制檔案載入進來。如下:

static{
		
		System.loadLibrary("hello");
}
        這裡需要注意的是:這裡的"hello",就是我們在Android.mk檔案中的 LOCAL_MODULE的值

       接下來就是,調運之前生成的二進位制檔案,我們只需要在MainActivity.java中這樣寫即可

Toast.makeText(this,helloWorldNdk(),Toast.LENGTH_LONG).show();
 此時,當執行我們的helloworld工程,即會彈出toast,顯示我們在hello.c中返回的字串。

       到這裡,一個最基本的ndk實現就完成了,現在我在加上一個宣告的方法:

public native String hello_World_Ndk();
      那麼,我們的hello.c中的方法應該怎麼寫呢?參照之前的寫法:

jstring Java_com_example_helloworld_MainActivity_hello_World_Ndk

這裡需要注意,這種寫法是錯誤的,ndk會以為我們是在MainActivity中的內部類hello,以及hello中的內部類World中的方法Ndk,這樣顯然是不對的,ndk為這種情況提供了一個標準,我們需要在方法中的每一個下劃線之後加上數字1即可,如下:

jstring Java_com_example_helloworld_MainActivity_hello_1World_1Ndk(JNIEnv* env ,jobject obj) {

	return (*env)->NewStringUTF(env,"i am from china");

} 
這一點,我們可以通過javah來生成,我在d:盤下新建一個MainActivity.java,內容如下:
public class MainActivity {
	public native String hello_World_Ndk();
}
然後,我們進入d:盤執行如下命令,生成.h檔案,如下圖:


此時在d:盤下生成了一個MainActivity.h檔案,內容如下:

JNIEXPORT jstring JNICALL Java_MainActivity_hello_1World_1Ndk
  (JNIEnv *, jobject);
可以看到是以這樣命名的。

        這裡有一點需要注意的是,如果我們的類是有包名的話,此時運用javah來生成.h檔案的時候,首先要將生成的.class檔案拷貝到對應的包地下,然後執行如下命令:

javah   包名.類名      這樣才可以生成.h檔案。

       那麼現在呢,有了以上這些基礎之後呢,就可以為MainActivity.java中宣告的native方法直接生成.h標頭檔案了,cmd進入命令列,首先進入helloworld工程的bin/classes目錄下,執行如下命令即可:

javah com.example.helloworld.MainActivity

此時,會在bin/classes資料夾下新生成一個com_example_helloworld_MainActivity.h檔案,我們將該檔案拷貝到jni目錄下,如下圖:


這個時候,我們的hello.c就可以這樣寫了:

#include<stdio.h>
#include<jni.h>
#include "com_example_helloworld_MainActivity.h"

/*
jstring Java_com_example_helloworld_MainActivity_helloWorldNdk(JNIEnv* env ,jobject obj) {

	return (*env)->NewStringUTF(env,"hello world");

} 

jstring Java_com_example_helloworld_MainActivity_hello_1World_1Ndk(JNIEnv* env ,jobject obj) {

	return (*env)->NewStringUTF(env,"i am from china");

} 
*/

JNIEXPORT jstring JNICALL Java_com_example_helloworld_MainActivity_helloWorldNdk
  (JNIEnv * env, jobject obj) {
  
	return (*env)->NewStringUTF(env,"hello world two");
}

JNIEXPORT jstring JNICALL Java_com_example_helloworld_MainActivity_hello_1World_1Ndk
  (JNIEnv * env, jobject obj) {
	
	  return (*env)->NewStringUTF(env,"i am from china two");
  
}

      此時執行helloworld工程,toast會一次彈出"hello world two"和"i am from china two"

總結一下:

1.首先需要宣告native方法:

public native String helloWorldNdk();
public native String hello_World_Ndk();
2.然後運用javah生成對應的.h標頭檔案

3.根據.h標頭檔案,編寫hello.c程式碼

4.編寫Android.mk檔案

#交叉編譯編譯c/c++程式碼所依賴的配置檔案

#獲取當前Android.mk的路徑  
LOCAL_PATH := $(call my-dir)

#變數初始化操作
include $(CLEAR_VARS)

#libhello.so 其實生成的libhello.so就是在我們這個模組的名稱前面加上lib後邊加上.so
LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c

include $(BUILD_SHARED_LIBRARY)

5.在java中通過靜態快引入二進位制檔案:

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

原始碼下載