本文将实现从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头文件。
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."); } } };
void throwInvalidTypeException(JNIEnv *pEnv); void throwNotExistingException(JNIEnv *pEnv); void throwStoreFullException(JNIEnv *pEnv);
//判断传入实例是否合法 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");