天天看點

Android的NDK開發~Hellow world!

1、到Google官網下載下傳adt-bundle----開發Android App的工具打包下載下傳,下載下傳後解壓即可,免去開發環境的配置。

     然後下載下傳NDK,建議下載下傳最新版本的,(ps:之前下載下傳過r8a的,有bug,導緻編譯很慢,r8b就沒有),配置ADT中的NDK Path。

     ~~這樣子就完成了NDK開發的全部準備了。

2、建立Android項目,step by step,這裡就不羅嗦了...

3、ADT v20之後的版本,提供了較為友善的NDK開發支援(ps:是以一定要更新ADT到最新版本哦),Android Tools-->Add Native Support :

Android的NDK開發~Hellow world!

 然後是所如so的名稱,也就是編譯後,庫的名稱,預設即可,之後可以在mk檔案上改的:

Android的NDK開發~Hellow world!

 完成後項目的結構會變成:

Android的NDK開發~Hellow world!

 我們重點關注jni檔案夾的内容,檔案裡放置的是我們的源碼(.h, .c, .cpp)和mk檔案(即make檔案,告訴編譯器應該如何進行編譯,詳細請自行Google),預設自動生成的cpp基本是空白的,mk檔案如下:

Android的NDK開發~Hellow world!

 上面mk檔案定義了編譯的路徑(LOCAL_PATH),include的路徑(BUILD_SHARED_LIBRARY),編譯後庫的名稱(LOCAL_MODULE),編譯源檔案的路徑(LOCAL_SRC_FILES)

 4、下面來一個例子:

 Scgps_Client.h

#include <string.h>
#include <jni.h>

#include <android/log.h>

#ifndef LOG_TAG
#define LOG_TAG "android-ndk"
#endif

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))      

    頭檔案除了引用到C的頭檔案之外,還引用了android方面的頭檔案log.h,用以實作列印log。

 Scgps_Client.c

#include <Scgps_Client.h>

JNIEXPORT jstring JNICALL Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz)
{
	const char * ret = "Hellow Form Ndk";

	LOGI("stationAndLines --> %s", ret);

        return (*env)->NewStringUTF(env, ret);
}
           

   .c檔案裡面就是實作Java的回調方法了,主要就是傳回字元串“Hellow From Ndk”。

 MainActivity.java

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		Toast.makeText(getApplicationContext(), sayHellow(), Toast.LENGTH_LONG).show();
	}
	
	public native String sayHellow();
	
	static {
              System.loadLibrary("Scgps_Client");
        }

}
           

    接着,我們一步步地解析:

    1、Java方面隻管調用,倒是很簡單,使用關鍵字native,聲明本地方法,然後在靜态代碼塊中加載本地庫。

    注意,loadLibrary的參數是庫的名稱,也就是mk檔案中LOCAL_MODULE定義的值。

    ps:之前也做過windows下Jni方面的開發,編譯出來是dll,然後Java加載的參數就是dll的名稱;

           但是ndk編輯的so檔案名稱卻是libScgps_Client,這裡有待研究...

Android的NDK開發~Hellow world!

    2、本地代碼方面,回調方法的定義 JNIEXPORT jstring JNICALL Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz); ,稍稍有點複雜,我盡量詳述:

         首先是方法名,Java_com_scgps_client_MainActivity_sayHellow,這裡與Java層是關系密切的,根據JNI的程式設計規範,調用的方法必須以Java開頭,然後較長的描述Java層native方法所在的詳細資訊,并以“_”分隔,區分大小寫!

         是以,sayHellow是Java聲明的本地方法,MainActivity是類名,com_scgps_client是包名。

         假如C定義的方法名稱與Java聲明的名稱對不上,運作則會報錯,抛出下面的資訊:

Android的NDK開發~Hellow world!

        友情提示:

        使用JDK的javah生成JNI的頭檔案!

        先編譯Java工程,之後項目目錄下會有bin檔案夾,bin存放編譯好的class檔案; 

        使用console,cd到bin目錄下:

javah -classpath . -jni com.scgps.client.MainActivity 
           

       然後是參數:JNIEnv* env 和 jobject thiz

       開講之前,先簡單說明一下,Jni中與Java基本資料類型的映射關系:

Android的NDK開發~Hellow world!

      定義都在jni.h的頭檔案中,各看官可自行查閱,基本上Java中的各種資料類型,jni中都有定義,也提供了相應的轉換方法,供C與Java之間傳遞。

      OK,現在讓我們繼續分析 Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz);

      JNIEnv* env  : 指向 JNI 函數表的指針變量。

      jobject thiz    :  該參數的意義取決于該方法是靜态還是執行個體方法(static or an instance method)。 

  •                           當本地方法作為一個執行個體方法時,第二個參數相當于對象本身,即 this;
  •                           當本地方法作為一個靜态方法時,指向所在類;

      在上面的例子中,Java_com_scgps_client_MainActivity_sayHellow是一個執行個體方法,因為thiz指向Java對象本身!

      最後是傳回值,jstring   

      很明顯,這是傳回Java字元串類型的值,通過 (*env)->NewStringUTF(env, ret); ,調用JNI函數表中的NewStringUTF方法,轉換ret指向的字元串,使其能被Java所識别。

      最後的最後,當然就是編譯、運作了:

      先看看現在的mk檔案:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Scgps_Client

LOCAL_SRC_FILES := \ Scgps_Client.c \

LOCAL_LDLIBS	:= -llog -landroid

LOCAL_STATIC_LIBRARIES	:= android_native_app_glue
	
include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)       

      因為引用到Android的庫,是以也要在這裡聲明一下,見LOCAL_LDLIBS和LOCAL_STATIC_LIBRARIES的值。

      總結幾個開發中的技巧:

           由于CDT(eclipse開發c/c++的插件)開發時,依賴編譯的結果,是以提示總是會滞後。如果提示源碼中有錯,或找不到include依賴時,不妨先檢視一下mk檔案是否已經描述清楚了,然後再rebuild一下。

           當整個編譯的結構有大的改動時,也不要忘了clear project,在build~~。

繼續閱讀