在上一篇,我们知道在JNI中一个jstring就代表一个String,并且事实上是一个java对象,也就是说经由JNI来传递java对象是完全可能的。但因为本地代码不能直接存取Java,所有Java对象都有一个共同的代表(表示方式):一个 jobject。
在本节中,我们将传递一个自定义的颜色对象(当然其它对象也同理可以)。主要在于如何保存一个对象在本地端,以及如何将其传回java端。本文将在上一篇的基础上进行。
更改如下:
1.添加自定义java类: Color.java
package com.packtpub;
public class Color {
private int mColor;
public Color(String pColor){
super();
mColor = android.graphics.Color.parseColor(pColor);
}
@Override
public String toString(){
return String.format("#%06X", mColor);
}
public int getColor(){
return mColor;
}
}
最终期望的颜色格式为:
#FF0000
2.更改用户类型支持:StoreType.java
public enum StoreType {
Integer, String, Color//
}
public native Color getColor(String pKey);
public native void setColor(String pKey, Color pColor);
4.在界面监听响应处添加对颜色对象操作的调用:StoreActivity.java
在类型选择中添加颜色项
//for get
case Color:
Log.d(TAG, "case Color" + mStore.getColor(lKey));
mUIValueEdit.setTextColor(mStore.getColor(lKey).getColor());
mUIValueEdit.setText("Color value : "
+ mStore.getColor(lKey));
break;
//for set
case Color:
mStore.setColor(lKey, new Color(mUIValueEdit.getText() .toString()));
break;
5.在jni/Store.h中添加本地代码对Color的支持: jni/Store.h
//支持类型枚举
typedef enum {
StoreType_Integer, StoreType_String, StoreType_Color//
} StoreType;
//值存储
typedef union {
int32_t mInteger;
char* mString;
jobject mColor;//
} StoreValue;
6.重新生成新的JNI头文件,并对新添加的颜色存取方法进行实现: jni/com_packtpub_Store.c
/*
* Class: com_packtpub_Store
* Method: getColor
* Signature: (Ljava/lang/String;)Lcom/packtpub/Color;
*/
JNIEXPORT jobject JNICALL Java_com_packtpub_Store_getColor
(JNIEnv *pEnv, jobject pThis, jstring pKey)
{
StoreEntry *lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if(isEntryValid(pEnv, lEntry, StoreType_Color))
{
return lEntry->mValue.mColor;
}
else
{
return NULL;
}
}
/*
* Class: com_packtpub_Store
* Method: setColor
* Signature: (Ljava/lang/String;Lcom/packtpub/Color;)V
*/
JNIEXPORT void JNICALL Java_com_packtpub_Store_setColor
(JNIEnv *pEnv, jobject pThis, jstring pKey, jobject pColor)
{
jobject lColor = (*pEnv)->NewGlobalRef(pEnv, pColor);
if(lColor == NULL)
return ;
StoreEntry *lEntry = allocateEntry(pEnv, &gStore, pKey);
if(lEntry != NULL)
{
lEntry->mType = StoreType_Color;
lEntry->mValue.mColor = lColor;
}
else
{
(*pEnv)->DeleteGlobalRef(pEnv, lColor);
}
}
注意:在SetColor( )中, 并不能简单的将jobject值存于store entry中, 因为在JNI方法中,通过参数传入的对象,都是局部引用。 局部引用在本地端超出方法范围后将会失效。
要使一个java对象在本地代码方法返回后仍可用,则它必须通知Dalvik VM:它被转化为全局引用并且不能被回收。JNI提供了这样的API以帮助达到此目的:NewGlobalRef( )与DeleteGlobalRef( )。在上述代码中,如果Store Entry实例创建不成功,那么全局引用会被删除。
7.最后,上述两个API必须配对使用。因此,在jni/Store.c中,还得更新releaseEntryValue( ) 方法:
case StoreType_Color:
(*pEnv)->DeleteGlobalRef(pEnv, pEntry->mValue.mColor);
break;
8.编译JNI,运行应用。
小结:
1.要想在本地端保存java对象,须先将对象在JNI方法中转为全局引用,并在适当时候释放它。
2.所有从Java代码中传递过来的对象都用一个jobject来代替。甚至是jstring,它本质上也是通过typedef关键字来重定义jobject的。因为本地代码调用受限于方法的边界,默认的,JNI使对象引用局限于方法内。这意味着,一个jobject对象只能在它被传入的方法内安全使用。事实上,Dalvik VM管理本地方法调用以及管理着本地方法被调用前后的java对象引用。但是,一个jobject只是一个指针,它不具有任何智能或者说是垃圾回收机制(毕竟,我们只想摆脱java,至少是部分地)。一旦本地方法返回了,Dalvik VM就没有办法知道本地代码是否仍然持有对象的引用并决定在任何时间回收它们。
3.全局引用也是在线程间共享变量的唯一方法,因为JNI上下文一直是处于线程局部内的。
4.从JNI中获取一个对象引用时,这个引用是局部的。若在后面的java代码中允许适当的垃圾回收,则这个引用(而不是引用所指的对象)会在方法返回时自动释放。也就是,缺省地,一个对象引用不能在其生命期(本地调用期间)之外有效。例如:
static jobject gMyReference;
JNIEXPORT void JNICALL Java_MyClass_myMethod(JNIEnv *pEnv,
jobject pThis, jobject pRef){
gMyReference = pRef;
}
这种方式是明令禁止的,使用这种方式将导致灾难(内存混乱或崩溃)
5.局部引用可以被显式地删除:
pEnv->DeleteLocalRef(lReference);
Java
虚拟机在同一时间根据要求可以保存至少
16
个引用,并且可以拒绝创建更多的引用。如果要如此,可以显示地通知它,例如:
pEnv->EnsureLocalCapacity(30);
上述显示地删除引用的好处是:
a.一个方法中局部对象的引用数目是有限的。当操作数组等大批量对象时,尽早删除不必要的引用很有益处;
b.可以尽快启动垃圾回收,以释放内存。