本文将实现从C/C++代码中抛出异常,并在JAVA端处理。
三个异常:值类型错误异常,键值不存在异常,存储满异常。
1.在java端新建异常包,并编写异常类:com.packtpub.exception,中添加如下三个相应的异常类文件
package com.packtpub.exception;
public class InvalidTypeException extends Exception{
public InvalidTypeException(String pDetailMessage){
super(pDetailMessage);
}
}
package com.packtpub.exception;
public class NotExistingKeyException extends Exception{
public NotExistingKeyException(String pDetailMessage){
super(pDetailMessage);
}
}
package com.packtpub.exception;
public class StoreFullException extends RuntimeException{
public StoreFullException(String pDetailMessage){
super(pDetailMessage);
}
}
2.在java端的本地方法声明处,添加异常:Store.java
package com.packtpub;
import com.packtpub.exception.InvalidTypeException;
import com.packtpub.exception.NotExistingKeyException;
/**
* @author Administrator
* @version 1.0
*/
/*
* 本类用于加载本地库,定义访问本地代码的接口
*/
public class Store {
static {
System.loadLibrary("store");
}
public native int getInteger(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setInteger(String pKey, int pInt);
public native String getString(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setString(String pKey, String pString);
public native Color getColor(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native void setColor(String pKey, Color pColor);
}
要从本地代码中抛出异常。因为异常不属于C语言,所以JNI异常不能在C方法原型处理声明(同样也不能在C++原型方法处声明,因为C++与JAVA有着不同的异常模型)。因此,这里不需要重新生成JNI头文件。
3.捕获异常:StoreActivity.java
private OnClickListener getListener = new OnClickListener() {
public void onClick(View v) {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = StoreType.values()[mUITypeSpinner
.getSelectedItemPosition()];
try {
switch (lType) {
case Integer:
Log.d(TAG, "case Integer");
mUIValueEdit.setText(Integer.toString(mStore
.getInteger(lKey)));
break;
case String:
Log.d(TAG, "case String");
mUIValueEdit.setText(mStore.getString(lKey));
break;
case Color:
Log.d(TAG, "case Color" + mStore.getColor(lKey));
mUIValueEdit.setTextColor(mStore.getColor(lKey).getColor());
mUIValueEdit.setText("Color value : "
+ mStore.getColor(lKey));
break;
}
} catch (NotExistingKeyException e) {
displayError("Key does not exist in store..");
} catch (InvalidTypeException e) {
displayError("Incorrect type.");
}
}
};
private OnClickListener setListener = new OnClickListener() {
public void onClick(View v) {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = StoreType.values()[mUITypeSpinner
.getSelectedItemPosition()];
try {
switch (lType) {
case Integer:
mStore.setInteger(lKey,
Integer.valueOf(mUIValueEdit.getText().toString()));
break;
case String:
mStore.setString(lKey, mUIValueEdit.getText().toString());
break;
case Color:
mStore.setColor(lKey, new Color(mUIValueEdit.getText()
.toString()));
break;
}
} catch (StoreFullException eStoreFullException) {
displayError("Store is full.");
}
}
};
4.在jni/Store.h中创建新的辅助方法以帮助抛出异常:
void throwInvalidTypeException(JNIEnv *pEnv);
void throwNotExistingException(JNIEnv *pEnv);
void throwStoreFullException(JNIEnv *pEnv);
5.在适当的地方抛出异常: jni/Store.c
//判断传入实例是否合法
int32_t isEntryValid(JNIEnv *pEnv, StoreEntry *pEntry,
StoreType pType)
{
if(pEntry == NULL)
throwNotExistingKeyException(pEnv);//////
else if(pEntry->mType != pType)
throwInvalidTypeException(pEnv);//////
else
return 1;
return 0;
}
//根据传入键名,在pStore里为其分配一个键名空间(分配一个实体,以在调用处写值)
StoreEntry* allocateEntry(JNIEnv *pEnv, Store *pStore, jstring pKey)
{
int32_t lError = 0;
StoreEntry *lEntry = findEntry(pEnv, pStore, pKey, &lError);
if(lEntry != NULL)
{
releaseEntryValue(pEnv, lEntry);
}
else if(!lError)
{
if(pStore->mLength >= STORE_MAX_CAPPCITY)
{
throwStoreFullException(pEnv);//////
return NULL;
}
lEntry = pStore->mEntries + pStore->mLength;
const char *lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey, NULL);
if(lKeyTmp == NULL)
{
return NULL;//add by me the 'NULL'
}
lEntry->mKey = (char*) malloc(strlen(lKeyTmp));
strcpy(lEntry->mKey, lKeyTmp);
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
++pStore->mLength;
}
return lEntry;
}
......
void throwNotExistingKeyException(JNIEnv* pEnv)
{
jclass lClass = (*pEnv)->FindClass(pEnv, "com/packtpub/exception/NotExistingKeyException");
if(lClass != NULL)
{
(*pEnv)->ThrowNew(pEnv, lClass, "Key does not exist.");
}
(*pEnv)->DeleteLocalRef(pEnv, lClass);
}
void throwInvalidTypeException(JNIEnv *pEnv)
{
jclass lClass = (*pEnv)->FindClass(pEnv, "com/packtpub/exception/InvalidTypeException");
if(lClass != NULL)
{
(*pEnv)->ThrowNew(pEnv, lClass, "Incorrect value.");
}
(*pEnv)->DeleteLocalRef(pEnv, lClass);
}
void throwStoreFullException(JNIEnv *pEnv)
{
jclass lClass = (*pEnv)->FindClass(pEnv, "com/packtpub/exception/StoreFullException");
if(lClass != NULL)
{
(*pEnv)->ThrowNew(pEnv, lClass, "Store is full.");
}
(*pEnv)->DeleteLocalRef(pEnv, lClass);
}
小结(抛出异常步骤):
1.在java端编写异常类,并在本地方法声明处添加异常声明(仅添加异常声明,不需要重新生成JNI头文件);
2.在上层Java端捕获异常,并处理;
3.在下层C、C++端抛出异常:先FindClass( )找到异常类描述,再ThrowNew( )抛出异常,最后删除类描述引用;
4.FindClass( )及其它JNI方法一般可能因多种原因调用失败(如内存不足,类未找到,等),因此,非常建议检查它们的返回值。
5.当一个异常抛出后,不要再进一步去调用JNI其它的清理方法(DeleteLocalRef(), Delete GlobalRef() 等)。本地代码应该清理它的资源并把控制权返给java,当然也有可能继续由纯本地代码处理,如果没有使用java代码的话。当本地方法返回后,虚拟机把异常传播给JAVA。
6.JNI与C++
C不是面向对象语言,而C++是。
在C中,JNIEnv实质上是一个包含了函数指针的结构体。当然,当一个JNIEnv传给你后,所有的函数指针已被初始化了,因此你可以向一个对象那样使用它。然而,对于pThis这个参数,它隐式地表明处于一个面向对象语言中。在C中,JNIEnv需要被“反引用(找到所指对象)”从而来调用一个方法:
jclass lClass = (*pEnv)->FindClass(pEnv, "com/packtpub/exception/StoreFullException");
//而C++看起来更自然和简单,JNIEnv是不需要“反引用”的,它看起来更像一个成员方法:
jclass lClass = pEnv->FindClass("com/packtpub/exception/StoreFullException");