標籤:android ndk
上一篇部落格,已經搭建好了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 __cplusplusextern "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後邊加上.soLOCAL_MODULE := helloLOCAL_SRC_FILES := hello.cinclude $(BUILD_SHARED_LIBRARY)
5.在java中通過靜態快引入二進位檔案:
static{ System.loadLibrary("hello");}
源碼下載
一步一步學習androidNDK編程(hello world)