編輯:關於Android編程
通過jni可以讓java和原生語言進行通信,這個通信不僅僅是信息傳遞,還包括方法間的調用,參數的傳遞。但是由於java的數據類型和原生語言的數據類型還是有所差異的,並且它們的實現機制不同,所以就需要將java中的對象和原生語言對象一一對應起來。在這個對應過程中,其實是很繁瑣並且開銷很大的,所以一般會SWIG自動生成代碼。但是為了學習這個過程,我們必須知道整個轉換的過程是怎樣的。
本文不講解jni項目的生成和調用過程,讀者如果需要可以自行學習也可以參考:了解jni的調用流程。
在java中,數據類型可以分為兩大類,一類是基本數據類型,一類是引用類型。
基本數據類型:bool,char,byte,short,int,long,float,double。
引用類型:除了基本類型以外的所有數據類型,包括數組。
通過jni的類型定義,可以讓java的基本數據類型和原生語言的基本類型相映射。意思就是說,通過jni.h頭文件,我們就可以通過jni頭文件中定義的類型在原生語言實現中來操作java對象。jni中類型定義的java基本數據類型與原生語言的基本類型對應關系如下表:
我們要知道的就是JNI類型對應的JAVA類型即可。
對於java的引用類型,它的內部數據結構並不向原生代碼公開,因此也就無法一一映射起來,但是在JNI類型中有一個jobject類型,基本是涵蓋了所有的引用類型的。而接下來看到的引用類型隱射表,所對應的原聲類型這一列對象全部都是jobject的子類。
對於以上的映射關系,我們必須清楚的知道它們所對應的java數據類型。
由於java的基本數據類型與原生語言的基本類型可以一一對應,那麼在使用的時候就不需要進行轉換了,直接使用對應的jni類型或原生語言類型。但是對於引用類型來說,它的轉換比較復雜,所以必須了解他的轉換過程。
由於引用類型對原生語言不是公開的,因此在使用的時候就必須進行轉換了。所以接下來就會講解各種轉換情況。
java的字符串類型被原生語言當作引用類型來處理,因此如果想在原生語言中使用字符串類型的數據,就必須進行轉換。JNI機制提供兩種編碼方式的的字符串格式,分別是Unicode和UTF編碼格式。針對不同的編碼格式有不同的轉換方法。
可以將c風格字符串轉換為jni類型的jstring然後返回給java對象使用。如果是Unicode編碼則使用NewString,UTF編碼則用NewStringUTF函數。比如:
env->NewStringUTF("來自C++");
java字符串在原生語言中被當作引用類型來處理,因此使用之前必須轉換為char數組類型。如果是Unicode的編碼使用GetStringChars,如果是UTF編碼則使用GetStringUTFChars。比如:
const char *p; jboolean isCopy = JNI_TRUE; p = env->GetStringUTFChars(param, &isCopy);
#define JNI_FALSE 0 #define JNI_TRUE 1
穿件了字符串指針,使用完畢後應該釋放從而避免內存洩漏。釋放方法如果是Unicode編碼使用releaseStringChars,如果是UTF編碼使用releaseStringUTFChars。比如
env->ReleaseStringUTFChars(javaString, p);
由於數組也被當作引用對象,所以JNI也提供函數對數組進行處理。
通過New
jintArray array1 = env->NewIntArray(10);
通過Get
jint nativeArray[10]; //將java數組轉換成c數組 env->GetIntArrayRegion(array, 0, 10, nativeArray);
由於通過Get
env->SetIntArrayRegion(array, 0, 10, nativeArray);
由於復制的代價很高,尤其是在數組元素很多的情況下,因此使用指針的方式會更加合理。通過Get
jint *pNative; jboolean isCopy=JNI_TRUE; pNative=env->GetIntArrayElements(array,&isCopy);
操作完成後,需要通過Release
env->ReleaseIntArrayElements(array,pnative,0);0表示釋放模式。總共有三種釋放模式:
復制回來的意思是,將原生數組的內容復制到java數組。
不僅java可以調用原生語言,原生語言同樣可以調用java對象。在java中,類有兩個域,分別是靜態域和實例域。一個類可以有多個實例域,但是這多個實例域都對應者一個靜態域。而在原生語言中獲取它們的方法也是不同的。
要想獲取類的對象的實例域或者靜態域,就必須知道類。獲取當前調用對象的類的方法如下:
jclass clazz; clazz = env->GetObjectClass(jTiss);
要想獲取java對象的實例域或者靜態域,除了知道所屬的類以外,還必須知道它的域ID。根據實例域和靜態域,獲取的方法也有所不同。
實例域,比如:
jfieldID fieldID; fieldID = env->GetFieldID(clazz, "instanceString", "Ljava/lang/String;");其中第二個參數表示java類中的實例變量的名稱,第三個參數是方法描述符,是Jni特有的一種。
靜態域,比如:
jfieldID fieldID; fieldID = env->GetStaticFieldID(clazz, "staticString", "Ljava/lang/String;");
有了上述的域ID,就可以獲取java的實例變量或者靜態變量了。
獲取實例變量:
jstring string; string = (jstring) env->GetObjectField(jTiss, fieldID);獲取靜態變量:
jstring string; string = (jstring) env->GetStaticObjectField(clazz, fieldID);
同樣的首先我們必須知道所調用的對象的類,接著需要知道方法ID:
獲取實例方法ID:
jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetMethodID(clazz, "getInstanceStringFromJava", "()Ljava/lang/String;");其中第二個參數是方法名稱。
獲取靜態方法ID:
jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetStaticMethodID(clazz,"getStaticStringFromJava", "()Ljava/lang/String;");
有了方法ID就可以調用了。
調用實例方法:
env->CallObjectMethod(jThis, methodID);
env->CallStaticObjectMethod(clazz, methodID);
上面基本涵蓋了最基礎的知識點,下面通過例子練手。
給出頭文件代碼:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include源文件:#include #include #include /* Header for class com_example_jnitestthree_CppUtils */ #ifndef _Included_com_example_jnitestthree_CppUtils #define _Included_com_example_jnitestthree_CppUtils #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_jnitestthree_CppUtils * Method: getStringFromCPP * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStringFromCPP (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_example_jnitestthree_CppUtils_add (JNIEnv *, jobject, jint, jint); /* * Class: com_example_jnitestthree_CppUtils * Method: putJavaStringToJni * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_putJavaStringToJni (JNIEnv *, jobject, jstring); /* * Class: com_example_jnitestthree_CppUtils * Method: testArray * Signature: ([I)[I */ JNIEXPORT jintArray JNICALL Java_com_example_jnitestthree_CppUtils_testArray (JNIEnv *, jobject, jintArray); /* * Class: com_example_jnitestthree_CppUtils * Method: getInstanceString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getInstanceString (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: getStaticString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStaticString (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: callInstanceMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callInstanceMethodByNative (JNIEnv *, jobject); /* * Class: com_example_jnitestthree_CppUtils * Method: callStaticMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callStaticMethodByNative (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
#include/* Class: com_example_jnitestthree_CppUtils * Method: getStringFromCPP * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStringFromCPP( JNIEnv *env, jobject jThiss) { return env->NewStringUTF("來自C++"); } /* * Class: com_example_jnitestthree_CppUtils * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_example_jnitestthree_CppUtils_add(JNIEnv *env, jobject jThiss, jint num1, jint num2) { //基本類型的java和C++通過jni類型映射起來了,因此可以直接使用 return num1 + num2; } /* * Class: com_example_jnitestthree_CppUtils * Method: putJavaStringToJni * Signature: (Ljava/lang/String;)V */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_putJavaStringToJni( JNIEnv *env, jobject jThiss, jstring param) { const char *p; jboolean isCopy = JNI_TRUE; p = env->GetStringUTFChars(param, &isCopy); jstring javaString = env->NewStringUTF(p); env->ReleaseStringUTFChars(javaString, p); return javaString; } /* * Class: com_example_jnitestthree_CppUtils * Method: testArray * Signature: ([I)Ljava/lang/String; */ JNIEXPORT jintArray JNICALL Java_com_example_jnitestthree_CppUtils_testArray( JNIEnv *env, jobject jThiss, jintArray array) { jintArray array1 = env->NewIntArray(10); jintArray sumArray = env->NewIntArray(10); jint nativeArray[10], nativeArray1[10], nativeSumArray[10]; //將java數組轉換成c數組 env->GetIntArrayRegion(array, 0, 10, nativeArray); env->GetIntArrayRegion(array1, 0, 10, nativeArray1); env->GetIntArrayRegion(sumArray, 0, 10, nativeSumArray); for (jint i = 0; i < 10; i++) { nativeArray1[i] = i + 1; nativeSumArray[i] = nativeArray[i] + nativeArray1[i]; } env->SetIntArrayRegion(sumArray, 0, 10, nativeSumArray); return sumArray; } /* * Class: com_example_jnitestthree_CppUtils * Method: getInstanceString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getInstanceString( JNIEnv *env, jobject jTiss) { //首先獲取類對象 jclass clazz; clazz = env->GetObjectClass(jTiss); //獲取要操作的對象的ID jfieldID fieldID; fieldID = env->GetFieldID(clazz, "instanceString", "Ljava/lang/String;"); jstring string; string = (jstring) env->GetObjectField(jTiss, fieldID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: getStaticString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_getStaticString( JNIEnv *env, jobject jTiss) { jclass clazz; clazz = env->GetObjectClass(jTiss); jfieldID fieldID; fieldID = env->GetStaticFieldID(clazz, "staticString", "Ljava/lang/String;"); jstring string; string = (jstring) env->GetStaticObjectField(clazz, fieldID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: callInstanceMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callInstanceMethodByNative( JNIEnv *env, jobject jThis) { jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetMethodID(clazz, "getInstanceStringFromJava", "()Ljava/lang/String;"); jstring string = (jstring) env->CallObjectMethod(jThis, methodID); return string; } /* * Class: com_example_jnitestthree_CppUtils * Method: callStaticMethodByNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_jnitestthree_CppUtils_callStaticMethodByNative( JNIEnv *env, jobject jThis) { jclass clazz = env->GetObjectClass(jThis); jmethodID methodID = env->GetStaticMethodID(clazz, "getStaticStringFromJava", "()Ljava/lang/String;"); jstring string = (jstring) env->CallStaticObjectMethod(clazz, methodID); return string; }
package com.example.jnitestthree; import java.io.ByteArrayOutputStream; public class CppUtils { static { System.loadLibrary("cppUtils"); } /** * 從CPP獲取字符串 * * @return */ public native String getStringFromCPP(); /** * 進行加操作 * * @param num1 * @param num2 * @return */ public native int add(int num1, int num2); /** * 將數組傳遞給jni * * @param param */ public native String putJavaStringToJni(String param); /** * 測試數組 * * @param array * @return */ public native int[] testArray(int[] array); public String getArrayByString(int[] array) { StringBuilder sb = new StringBuilder(); int[] sum = testArray(array); for (int i = 0; i < sum.length; i++) { sb.append(sum[i] + ","); } return sb.toString(); } public String instanceString = "instanceString"; public static String staticString = "staticString"; /** * 原生語言調用的對象實例和靜態實例 * * @return */ public native String getInstanceString(); public native String getStaticString(); /** * 原生語言調用的實例方法和靜態方法 */ public String getInstanceStringFromJava() { return "instanceStringFromJava"; } public native String callInstanceMethodByNative(); public static String getStaticStringFromJava() { return "staticStringFromJava"; } public native String callStaticMethodByNative(); }
運行結果:
Android的待機狀態管理由PowerManagerService.java管理主要的狀態更新方法在下面貼出代碼, 注釋寫的很清楚, 第一次看系統源碼感覺還比較爽主要是
在編寫自定義滑動控件時常常會用到Android觸摸機制和Scroller及VelocityTracker。Android Touch系統簡介(二):實例詳解onInte
一、批量打包1、集成了友盟統計,並在AndroidManifest.xml中添加了如下代碼<meta-dataandroid:name=UMENG_CHANNELa
以前編程的時候,遇到倒計時的功能時,經常自己去寫,但其實Android已經幫封裝好了一個倒計時類CountDownTimer,其實是將後台線程的創建和Handler隊列封