打通了连接Java世界和native世界的通道之后,摆在我们面前的问题,就变为了,如何在native code中,来操作Java object呢?Java object可以分为如下3种:
Java Language的基本数据类型,与C/C++中的那些基本数据类型并无太大的差别,无论是作为参数传递,还是要作为返回值。因而此处,对于Java Language的基本数据类型就不再多做描述。下面主要说明一下在native code中访问Java Language中内置的引用数据类型和非Java Language内置的引用数据类型。
Java Language中内置的引用数据类型,又主要包括String和原始数据类型的数组。
首先来看String。String所对应的native 类型为jstring。jstring既不同于C风格的一‘\0’结尾的char *的string,也不同于C++标准库中的string类型。我们不能像操作C风格的string那样来操作jstring,而必须首先经过JNI函数的转换,转换为UTF-16编码的jchar array或者UTF-8编码的char array之后,再来做操作。JNI有专门提供一组函数来来完成这样的转换。这组函数如下:
可以看到这组函数大体上可以分为两个族,String*的那一族用于操作UTF-16的字串串,如将jstring转换为UTF-16编码的jchar 数组,获取jchar数组长度等;而StringUTF*的那一族,则用于操作UTF-8的字串。
接下来,可以看一段code。首先,是在Java code中调用native 方法的部分:
char[] text = new char[] { 0xE01, 0xE49, 0xE33, 0xE43, 0xE37, 0xE27, 0xE64 }; stringToJNI(String.valueOf(text, 0, text.length)); } public native String stringToJNI(String text);
然后是C/C++ code中对于native method的实现部分:
static jstring HelloJni_stringToJNI( JNIEnv* env, jobject thiz, jstring text) { JniDebug("from HelloJni_stringToJNI"); const char* utf8Chars = env->GetStringUTFChars(text, NULL); jsize utf8Length = env->GetStringUTFLength(text); JniDebug("utf8Length = %d", utf8Length); if (utf8Chars == NULL) { return NULL; } JniDebug("String got from java world: %s", utf8Chars); env->ReleaseStringUTFChars(text, utf8Chars); const jchar* chars = env->GetStringChars(text, NULL); jsize unicodeLength = env->GetStringLength(text); JniDebug("unicodeLength = %d", unicodeLength); env->ReleaseStringChars(text, chars); return text; }
上面那段code的运行结果:
这个地方我们看到,有用到GetStringChars和GetStringUTFChars及ReleaseStringChars和ReleaseStringUTFChars这几个函数,GetStringChars和GetStringUTFChars返回jstring经过转换的结果,对于这两个函数的第三个参数,如果isCopy不是NULL,则当做了copy时,*isCopy会被设置为JNI_TRUE,而没有做copy时,它被设置为JNI_FALSE。尤其值得我们注意的是,每次调用GetString*函数之后,总是需要在适当的时候,来相应的调用ReleaseString*函数。否则,则会出现Leak,而产生一些莫名其妙的NE。另外,在Java code中,我们看到String不是由ASCII范围内的字符,而是泰语字符的char[]转换而来。再来看native code中,传递给android log函数的参数,是java层的字串转换为UTF-8编码的结果。因而,我们了解到一点,传递给android log函数参数,应该是一个UTF-8编码的字串。我们知道,UTF-8不仅可以兼容ASCII,还可以支持完整的Unicode字符。
GetStringRegion和GetStringUTFRegion这两个函数,所做的事情,与GetStringChars和GetStringUTFChars所做的事情,并没有很大的区别。最大的区别在于,前者会将jstring转换的结果copy进作为参数传递进去的buffer中。
GetStringCritical和ReleaseStringCritical这两个调用之间的code,则应被当作是处于一个临界区(“critical region”)。处于临界区的code,会有额外的一些限制。Inside a critical region, native code must not call other JNI functions, nor may the native code make any system call that may cause the current thread to block and wait for another thread in the virtual machine instance。当然,这两个函数的具体实现,则因Java VM的实现而异。
创建jstring的函数看起来也还清晰明确,无论是从UTF-8编码的char *创建,还是从UTF-16编码的jchar *创建,JNI提供的接口都简单易用。
然后来看数组。对于基本数据类型的数组的访问,与对于String的访问,有很多相似的地方。我们同样不能直接访问这些数组,而必须要经过JNI的转换。这种转换的接口也是分为3组,Get<Type>ArrayElements()这一组,会直接返回一个<NativeType>的指针,之后我们就可以使用C/C++中常规的访问数组的方法来访问基本数据类型的数组了。同样的,我们一样需要在适当的地方调用相应的Release<Type>ArrayElements()函数以防止Leak;Get<Type>ArrayRegion()这一组,则可以将数组的成员copy进我们传入的一个buffer中;而GetPrimitiveArrayCritical()则与前面的那个GetStringCritical()类似。我们可以来看一下JNI提供的那些函数的原型,及简单的说明:
接下来我们可以看一些code。首先,是Java code中对于native方法的调用的部分:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText(stringFromJNI()); setContentView(tv); int[] dataElement = new int[] { 2, 3, 4, 6 }; int sum = sumIntWithNative(dataElement, 0, dataElement.length); Log.d(TAG, "sum = " + sum); double[] doubleElement = new double[] { 3.4, 5.3, 7.6, 9.2 }; double doubleSum = sumDoubleWithNative(doubleElement, 0, doubleElement.length); Log.d(TAG, "doubleSum = " + doubleSum); char[] text = new char[] { 0xE01, 0xE49, 0xE33, 0xE43, 0xE37, 0xE27, 0xE64 }; stringToJNI(String.valueOf(text, 0, text.length)); } public native String stringToJNI(String text); public native int sumIntWithNative(int[] dataElement, int start, int end); public native double sumDoubleWithNative (double[] dataElement, int start, int end);
然后,是native code中对于native方法的实现的部分:
static jint HelloJni_sumIntWithNative( JNIEnv* env, jobject thiz, jintArray dataElement, jint start, jint end) { JniDebug("from HelloJni_sumIntWithNative"); jint sum = 0; jint*dateArray = env->GetIntArrayElements(dataElement, NULL); for (int i = start; i < end; ++ i) { sum += dateArray[i]; } env->ReleaseIntArrayElements(dataElement, dateArray, 0); return sum; } static jdouble HelloJni_sumDoubleWithNative( JNIEnv* env, jobject thiz, jdoubleArray dataElement, jint start, jint end) { JniDebug("from HelloJni_sumDoubleWithNative"); jdouble sum = 0.0; jdouble *dataArray = env->GetDoubleArrayElements(dataElement, NULL); for (int i = start; i < end; ++ i) { sum += dataArray[i]; } env->ReleaseDoubleArrayElements(dataElement, dataArray, 0); return sum; }
程序运行过程中所打的log:
待续...