編譯:把一個源文件 源代碼 翻譯(編譯)成一個二進制文件的過程
連接:把編譯生成的二進制,根據操作系統,根據當前處理器的類型.把這個二進制文件轉化成一個真正可以執行的二進制文件.
交叉編譯: 在一個操作系統平台(一種cpu的平台)上 編譯出來另外一個操作系統平台上(另外一種cpu上)可以運行的代碼.
.c-> .exe
.c-> elf 可執行的二進制文件
交叉編譯,借助於一個工具鏈,工具鏈可以模擬目標平台的一些環境,把編譯後的二進制文件鏈接成一個目標平台下可以執行的二進制代碼.
1.創建一個android工程
2.JAVA代碼中寫聲明native 方法 public native String helloFromJNI();
3.創建jni目錄,編寫c代碼,方法名字要對應
4.編寫Android.mk文件
5.Ndk編譯生成動態庫
6.Java代碼load 動態庫.調用native代碼
1、創建一個android工程:
2、在DemoActivity類中定義一個 native方法:
public class DemoActivityextends Activity {
//聲明本地native方法 注意沒有方法體
public native String helloFromJNI();
}
3、在應用程序目錄下創建一個jni目錄:在內部寫Hello.c 的c文件。C文件如下:
#include
#include
//通過 包名_類名_方法名-完成 java-c代碼的映射
//方法名固定寫法:Java_包名_類名_本地方法名(JNIEnv* env, jobject obj)
jstring Java_cn_itcast_ndk_DemoActivity_helloFromC(JNIEnv*env,
jobject obj) {
//*env 得到了 JNIEnv
// 因為JNIEnv 是JNINativeInterface的指針類型
// **env 得到JNINativeInterface結構體
char* str = "hellofrom c";
// return (**env).NewStringUTF(env,str);
return (*env)->NewStringUTF(env,str);
}
4、在jni目錄下 編寫Android.mk文件:
該文件中指定要編譯的源文件的名稱和編譯出來的可執行文件的名稱。
內容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#告訴編譯器編譯出來的可執行性文件叫什麼名字
LOCAL_MODULE := Hello
#告訴編譯器 編譯的源文件的名稱
LOCAL_SRC_FILES := Hello.c
include $(BUILD_SHARED_LIBRARY)
5、Ndk編譯生成動態庫:(交叉編譯)
使用Cygwin 工具到達工程的jni目錄下/cygdrive/g/androidSpace/myndk/jni
使用ndk-build命令生成一個libHello.so文件,該文件被放在當前工程下的libs/armeabi/目錄下。
生成的libHello.so是一個linux系統下可執行的二進制文件。
6、使用Java代碼load 動態庫.調用native代碼:
就是將可執行的二進制文件(.so的c代碼庫文件)加載到java虛擬機中:
在靜態代碼塊使用 System.loadLibrary("");方法
public class DemoActivityextends Activity {
static{
//把 的c代碼的庫文件加載到java虛擬機裡面
// 不要寫so的前綴lib 也不要寫擴展名.so
System.loadLibrary("Hello");
}
//聲明本地native方法 注意沒有方法體
public native StringhelloFromJNI();
@Override
public voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
}
//點擊按鈕的事件:
public void click(View v){
String str = helloFromJNI();
Toast.makeText(this, str, 1).show();
}
}
在含有native方法的java源文件的目錄下,下使用javac 將.java文件編譯成.class文件,
再使用javah 就會在當前目錄下 生成 .h文件 該文件中有生成對應的c的方法名。
G:\androidSpace\myndk\bin\classes>javah com.li.myndk.DemoActivity
1.創建一個android工程
2.JAVA代碼中寫聲明native 方法 public native String helloFromJNI();
3.用javah工具生成頭文件
來到 工程classes目錄下執行javah 命令:javahcom.li.myndk.DemoActivity
命令為: javah 全類名
在classes目錄下就生成com_li_myndk_DemoActivity.h文件
4. 在工程下創建jni目錄,引入頭文件,根據頭文件實現c代碼
將com_li_myndk_DemoActivity.h文件拷貝到jni目錄下
編寫Hello.c文件: 在.C文件中引入 .h文件。
#include
#include"cn_itcast_ndk2_DemoActivity.h"
char* str = "abc ";
// return (**env).NewStringUTF(env, str);
return (*env)->NewStringUTF(env, str);
}
5.編寫Android.mk文件
6.Ndk編譯生成動態庫
7.Java代碼load 動態庫.調用native代碼
1. 忘記寫android.mk文件,或者路徑不正確
Android NDK: Your APP_BUILD_SCRIPT pointsto an unknown file: ./jni/Android.mk
2. android.mk文件語法錯誤
jni/Android.mk:4: *** 遺漏分隔符 。 停止。
3. c代碼的語法出現問題
make: ***[obj/local/armeabi/objs/Hello/Hello.o] Error 1
一般出現了error 1 錯誤 是c代碼的語法出現了問題.
需要先去查看第一個error對應的內容
error: 'intt' undeclared (first use inthis function)
4.加載庫文件出錯,庫文件不存在 Library Hel1o not found
06-03 03:42:03.817:ERROR/AndroidRuntime(13632): Caused by: java.lang.UnsatisfiedLinkError: LibraryHel1o not found
06-03 03:42:03.817:ERROR/AndroidRuntime(13632): atjava.lang.Runtime.loadLibrary(Runtime.java:461)
06-03 03:42:03.817:ERROR/AndroidRuntime(13632): atjava.lang.System.loadLibrary(System.java:557)
06-03 03:42:03.817:ERROR/AndroidRuntime(13632): atcn.itcast.ndk2.DemoActivity.
06-03 03:42:03.817:ERROR/AndroidRuntime(13632): ... 15more
5.c的代碼庫沒有被加載到java虛擬機裡面(1.忘記加載了c代碼庫,2.c代碼庫裡面沒有與java裡面native的方法對應的代碼)
06-03 03:43:35.768:ERROR/AndroidRuntime(13972): FATAL EXCEPTION: main
06-03 03:43:35.768:ERROR/AndroidRuntime(13972): java.lang.UnsatisfiedLinkError: hello_from_c
06-03 03:43:35.768: ERROR/AndroidRuntime(13972): atcn.itcast.ndk2.DemoActivity.hello_from_c(Native Method)
6.控制台打印Buildfingerprint: 程序界面顯示一下就消失了.
原因就是c代碼裡面的邏輯有問題,參數內存洩露,運行時的異常. 比如指針的類型不匹配
修改通過打印log的方式 定位出來錯誤究竟出現在哪一行的代碼.
06-03 03:48:55.132: INFO/DEBUG(31): ****** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
06-03 03:48:55.132: INFO/DEBUG(31): Buildfingerprint: 'generic/sdk/generic/:2.2/FRF91/43546:eng/test-keys'
06-03 03:48:55.132: INFO/DEBUG(31): pid:15204, tid: 15204 >>>cn.itcast.ndk2 <<<
06-03 03:48:55.132: INFO/DEBUG(31):signal 11 (SIGSEGV), fault addr 8090214d
06-03 03:48:55.132: INFO/DEBUG(31): r0 8090214d r1 80902154 r2 00000006 r3 6e61687a
1、在Android.mk文件增加LOCAL_LDLIBS += -llog
#引入liblog.so 文件: 向logcat控制台輸出log對應的庫文件
LOCAL_LDLIBS += -llog
2、C代碼中增加
#include
#defineLOG_TAG "System.out"
#defineLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#defineLOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
LOGI("info\n");
LOGD("debug\n");
例:
#include
#include"cn_itcast_ndk2_DemoActivity.h"
#include
#define LOG_TAG "System.out"
#define LOGD(...)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
JNIEXPORT jstring JNICALLJava_cn_itcast_ndk2_DemoActivity_hello_1from_1c
(JNIEnv * env, jobject obj){
LOGD("begin");向控制台打印log 打印出begin
//定義一個字符串的常量 (不能被修改的變量)
constchar* str1 = "hello";
LOGD("INITSTR1");
constchar* str2 = "zhang san";
LOGD("initstr2");
LOGD("str1=%s",str1);
LOGD("str2=%s",str2);
//c代碼不允許修改一個字符串的常量
strcat(str1,str2);//合並兩個字符串方法
LOGD("strcat");
char*str = "你好 ,中國";
return(*env)->NewStringUTF(env, str);
}
1、ndk-r6以上的版本解決中文亂碼問題
①、把c語言的源文件改為 utf-8的格式
②、在調用c代碼的時候,模擬器就可以顯示出來中文了...
ndk-r6以上的版本 在編譯鏈接 c語言的源文件的時候 采用的編碼格式是utf-8
保證所有的c代碼的源文件也要是utf-8
android上在顯示 字符串的時候 默認的語言集是utf-8
2、ndk-r4老的ndk版本, 在編譯源代碼的采用的編碼集是西歐的編碼集iso8859-1的編碼: 所以在獲取中文是iso8859-1的編碼格式。
String str = hello_from_c();
new String(str.getBytes("iso8859-1"),"utf-8");
# $ 代表調用一個makefile的函數
# call my-dir這個函數的作用就是獲取當前文件所在的目錄賦給 LOCAL_PATH變量
LOCAL_PATH := $(call my-dir)
# 重新初始化 gnu make的腳本環境
include $(CLEAR_VARS)
#上面的CLEAR_VARS方法 會把所有的編譯環境的變量重新初始化,不會初始化 LOCAL_PATH。
#告訴編譯器編譯出來的可執行性文件叫什麼名字,編譯完以後會變成libHello.so
LOCAL_MODULE := Hello
#告訴編譯器 編譯的源文件的名稱
LOCAL_SRC_FILES := Hello.c
#liblog.so 向logcat控制台輸出log對應的庫文件
LOCAL_LDLIBS += -llog
#代表將該c代碼最終會變成一個動態庫 擴展名為.so
include $(BUILD_SHARED_LIBRARY)
1、如果在android.mk文件中放置下面這一句:
include$(BUILD_SHARED_LIBRARY)
代表c代碼最終會變成一個動態庫 擴展名為.so
2、如果在android.mk文件中放置下面這一句:
include $(BUILD_STATIC_LIBRARY)
代表最終c代碼會編譯成一個靜態庫 擴展名為.a
3、動態庫與靜態庫比較:
一般來說 ,動態庫的體積要比靜態庫的體積小很多.
動態庫: 動態的,代碼的執行的時候 依賴的函數 依賴的c代碼都是動態的加載執行的.
靜態庫: 靜態的,代碼在執行之前,所有依賴的庫函數 ,所有依賴的代碼,都必須預先加載編譯到文件裡面.
4、什麼時候使用靜態庫:
當你的公司,有一個需求:把某一個函數,某一個功能做成一個模塊 供別的開發人員使用的時候
1、創建一個android工程:在內部定義一個類:在類中定義一下幾個本地方法:
public class DataProvider{
/**
* 把java中的兩個int類型的數據 傳遞給c語言,
* c語言在得到這兩個數據之後,對這兩個數據進行求和
* 返回回來的結果 相加後的值
* @param x
* @param y
* @return
*/
public native int add(int x ,int y);
/**
* 把java中的一個字符串傳遞個c語言
* c語言得到這個字符串之後對這個字符串進行一些操作
* 在字符串後面添加一個 你好.
* @param s
* @return
*/
public native StringsayHelloInC(String s);
/**
* 把java中的一個int數組 傳遞給c語言 ,
* c語言獲取到這個int數組之後,把數組的每一個元素的值都+10
* 把相加操作之後 新的數組返回給java代碼
* @param iNum
* @return
*/
public native int[] intMethod(int[] iNum); // 圖片 或者 音頻 視頻處理的時候 需要把一個數組傳遞給c代碼
}
2、編寫c代碼:Hello.c 如下:分別完成以上的操作:
#include
#include "cn_itcast_ndk3_DataProvider.h"
#include
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__)
/**
* 工具方法
* 作用: 把java中的string 轉化成一個c語言中的char數組
* 接受的參數 envjni環境的指針
* jstr 代表的是要被轉化的java的string 字符串
* 返回值 : 一個c語言中的char數組的首地址 (char 字符串)
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid =
(*env)->GetMethodID(env,clsstring,"getBytes","(Ljava/lang/String;)[B");
// String .getByte("GB2312");
jbyteArray barr=
(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0); //
return rtn;
}
//計算兩個數和的方法
JNIEXPORT jint JNICALL Java_cn_itcast_ndk3_DataProvider_add
(JNIEnv * env, jobject obj, jint x, jint y){
LOGD("x=%d",x);
LOGD("y=%d",y);
int result = x + y;
LOGD("result=%d",result);
return result;
}
//拼接字符串的方法
JNIEXPORT jstring JNICALL Java_cn_itcast_ndk3_DataProvider_sayHelloInC
(JNIEnv * env , jobjectobj , jstring jstr){
//1.把java中的string 轉化成 c語言裡面的char數組
char* cstr = Jstring2CStr(env,jstr);
LOGI("cstr=%s",cstr);
char* hellostr ="hello";
strcat(cstr,hellostr); //拼接兩個字符串
LOGI("new cstr=%s",cstr);
return (*env)->NewStringUTF(env,cstr);
}
//操作數組的方法
JNIEXPORT jintArray JNICALL Java_cn_itcast_ndk3_DataProvider_intMethod
(JNIEnv * env , jobjectobj , jintArray jarr){
// 獲取jarr的長度
// jsize (*GetArrayLength)(JNIEnv*, jarray);
//調用jni方法獲取數組的長度
int len =(*env)->GetArrayLength(env,jarr);
LOGI("len =%d",len);
// jint* (*GetIntArrayElements)(JNIEnv*,jintArray, jboolean*);
//獲取數組的首地址
int* carr = (*env)->GetIntArrayElements(env,jarr,0);
int i;
for(i=0;i
LOGI("arr[%d]=%d",i,*(carr+i));
*(carr+i) = *(carr+i)+10;
}
return jarr;
}
3、注意:
①、在c中接收java中int類型的數據時:由於int類型的數據java和c中的長度相同,c可以直接使用java傳遞過來int型數據。
②、c接收了java中的字符串數據 ,由於c中沒有字符串類型,首先c要先將java的字符串類型轉成char數組類型;
在c語言中 字符串的結果都是以/0這樣一個標示 作為字符串的結尾
下面是具體的轉換方法:
/**
* 工具方法
* 作用: 把java中的string 轉化成一個c語言中的char數組
* 接受的參數 envjni環境的指針
* jstr 代表的是要被轉化的java的string 字符串
* 返回值 : 一個c語言中的char數組的首地址 (char 字符串)
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid =
(*env)->GetMethodID(env,clsstring,"getBytes","(Ljava/lang/String;)[B");
// String.getByte("GB2312");
jbyteArray barr=
(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0)
{
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0); //
return rtn;
}
③、在將java 中int類型的數組傳遞c代碼中 ,獲取數組的長度,再獲取數組的首地址後獲取每一個元素。
④、java不會將字符串數組傳給c代碼, 而是將字符串數組元素用:隔開轉成字符串傳給c代碼.
十二、c代碼調用java代碼:案例:c代碼調用java 代碼 彈出一個土司.
主要步驟:首先java調用c代碼的方法 在c代碼中再調用java代碼:
1、在java類中定義方法:
public class DemoActivity extends Activity {
static{
System.loadLibrary("Hello");
}
public native void callMethod1();
//按鈕的點擊事件
public void showToast(View view){
//1.調用底層的c代碼
//2.c代碼裡面 讓c語言的代碼調用java代碼 顯示出來一個土司
callMethod1();
}
//被c調用的方法
public void javaShowToast(String message){
Toast.makeText(this, message, 0).show();
}
}
2、c代碼:類似java中的反射,先獲取java類的字節碼文件,再獲取method對象,再執行該方法。
#include
#include "cn_itcast_ndkcallback_DemoActivity.h"
#include
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__)
//參數env 是JNIEnv的指針
// obj 代表的是調用這個c代碼的java對象
JNIEXPORT void JNICALLJava_cn_itcast_ndkcallback_DemoActivity_callMethod1
(JNIEnv * env , jobject obj) {
// 找到 java中的代碼javaShowToast 調用他 讓java代碼顯示出來一個土司
//
// Method method = DemoActivity.class.getMethod("javaShowToast",new Class[]{String.class});
// method.invoke(obj, "string");
// 1. 尋找要調用的java代碼的字節碼 class
//jclass (*FindClass)(JNIEnv*, const char*);
//獲取java的類的字節碼。FindClass方法參數(env ,java類類路徑)
jclass jclazz = (*env)->FindClass(env,"cn/itcast/ndkcallback/DemoActivity");
// 2.尋找當前jclass裡面的方法的id
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*,const char*);
//獲取字節碼中指定方法的id。GetMethodID方法參數(env ,字節碼文件,方法名, 該方法簽名)
//方法簽名通過javap 工具獲得;
jmethodID jshowtoastmethod =
(*env)->GetMethodID(env,jclazz,"javaShowToast","(Ljava/lang/String;)V");
//3. 調用該方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
//執行方法:CallVoidMethod方法參數(env,obj ,方法id,傳遞到被調用java方法中的參數)
(*env)->CallVoidMethod(env,obj,jshowtoastmethod,(*env)->NewStringUTF(env,"hi from c"));
}
3、注意: 獲取方法簽名的方法是:在命令行中使用javap 工具:
命令為: javap -s 完整類名 。 就可以獲取指定類中所有方法的方法簽名。
1、定義被c代碼調用的java的方法
public class DataProvider{
//C調用java空方法
public voidhelloFromJava(){
System.out.println("我是java的空方法");
}
//C調用java中的帶兩個int參數的方法
public int Add(int x,int y){
int result =x+y;
System.out.println("相加的結果為"+result);
return result;
}
//C調用java中參數為string的方法
public voidprintString(String s){
System.out.println("我是java方法"+s);
}
}
2、c代碼的方法:
①、調用java中的helloFromJava()方法
//obj 代表的是調用這個c代碼的java對象
JNIEXPORT void JNICALLJava_cn_itcast_ndkcallback_DemoActivity_callMethod2
(JNIEnv * env , jobject obj) {
//獲取字節碼
jclass jclazz =
(*env)->FindClass(env,"cn/itcast/ndkcallback/DataProvider");
// jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
//如果一個java方法是 靜態的 就必須通過 GetStaticMethodID
//獲取方法的id
jmethodIDjvoidmethod =
(*env)->GetMethodID(env,jclazz,"helloFromJava","()V");
//必須在這個地方 創建出來 dataprovider的對象
// jobject (*AllocObject)(JNIEnv*, jclass);
//創建java中dataprovider類的對象
jobject dpobj =(*env)->AllocObject(env,jclazz);
//調用方法:
(*env)->CallVoidMethod(env,dpobj,jvoidmethod);
}
②、調用java中Add(int x,int y)方法:
JNIEXPORT void JNICALLJava_cn_itcast_ndkcallback_DemoActivity_callMethod3
(JNIEnv * env , jobject obj) {
jclass jclazz =
(*env)->FindClass(env,"cn/itcast/ndkcallback/DataProvider");
jmethodID jintmethod =(*env)->GetMethodID(env,jclazz,"Add","(II)I");
jobject dpobj =(*env)->AllocObject(env,jclazz);
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
//調用帶有int類型返回值的方法:
intresult =(*env)->CallIntMethod(env,dpobj,jintmethod,3,5);
LOGI("c resutl=%d",result);
}
③、調用java中printString(Strings)方法:
JNIEXPORT void JNICALLJava_cn_itcast_ndkcallback_DemoActivity_callMethod4
(JNIEnv * env , jobject obj) {
jclass jclazz =
(*env)->FindClass(env,"cn/itcast/ndkcallback/DataProvider");
jmethodID jvoidmethod =
(*env)->GetMethodID(env,jclazz,"printString","(Ljava/lang/String;)V");
jobject dpobj =(*env)->AllocObject(env,jclazz);
(*env)->CallVoidMethod(env,dpobj,jvoidmethod,(*env)->NewStringUTF(env,"haha from c"));
}
十三 c代碼調用java中的靜態方法:
1、在類中定義一個靜態方法:
public class DataProvider {
//靜態方法
public static void hellofromstatic(){
System.out.println("我是靜態方法");
}
}
2、c代碼:
//obj 代表的是調用這個c代碼的java對象
JNIEXPORT void JNICALLJava_cn_itcast_ndkcallback_DemoActivity_callMethod2
(JNIEnv * env , jobject obj) {
//獲取類字節碼
jclass jclazz =
(*env)->FindClass(env,"cn/itcast/ndkcallback/DataProvider");
// jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
//如果一個java方法是 靜態的 就必須通過 GetStaticMethodID
//獲取靜態方法id 使用GetStaticMethodID方法
jmethodID jvoidmethod =
(*env)->GetStaticMethodID(env,jclazz,"hellofromstatic","()V");
/// void (*CallStaticVoidMethod)(JNIEnv*,jclass, jmethodID, ...);
//執行靜態方法
(*env)->CallStaticVoidMethod(env,jclazz,jvoidmethod);
}