Android JNI介绍(四)- 异常的处理

上一篇文章中,咱们已经了解了如何进行Java和Native的交互,本文将介绍在JNI中如何进行Java异常处理。java

1、主动抛出异常

在Java中咱们能看到一些类中声明的native函数会抛出异常,好比:android

java.io.FileOutputStream类中的:git

private native void open0(String name, boolean append)
        throws FileNotFoundException;
复制代码

android.hardware.Camera类中的:github

public native final void setPreviewSurface(Surface surface) throws IOException;
    public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;
复制代码

这些函数的实现都在native层,可是其声明却包含了可能会出现的异常,那就说明native实际上是能够向Java抛异常的;
JNI也能够进行异常的捕获,咱们也来看一下如何在native进行异常的处理。windows

2、使用JNI提供的函数处理异常

  • 函数介绍bash

    JNI为咱们提供了一些函数进行异常的处理,以下:app

    // CATCH
        
        // 若是有异常出现,返回值就是异常,若是没有异常,回传NULL
        jthrowable ExceptionOccurred()
        // 内部会调用异常对应的Java类的printStackTrace()函数
        void ExceptionDescribe()   
        // 清除异常,清除异常后能够继续进行JNI操做
        void ExceptionClear()
        // 当前有没发生异常,若发生了,回传JNI_TRUE,不然回传JNI_FALSE
        jboolean ExceptionCheck()
        
        // THROW
        
        // 向Java抛出一个异常
        jint Throw(jthrowable obj)
        // 选择异常的类型,填写异常的信息进行抛出
        jint ThrowNew(jclass clazz, const char* message)
    复制代码
  • 捕获异常ide

    举个例子:函数

    jmethodID toURLID = env->GetMethodID(fileClazz,"toURL","()Ljava/net/URL;");
        jobject toURLValue = env->CallObjectMethod(fileObj, toURLID);
        // this exception may occur:
        // java.net.MalformedURLException
        jthrowable error = env->ExceptionOccurred();
        if (error != NULL) {
            // 出现了异常
        }else{
            // 没出现异常
        }
    复制代码
  • 抛出异常工具

    须要注意的是,这里抛出的是Throwable,也就是说,不只能够抛出Exception,还能抛出Error。 使用方法以下:

    • Java
    public class CustomException extends Exception {
        CustomException() {
            super();
        }
    
        CustomException(String message) {
            super(message);
        }
    }
    复制代码
    public native String testExceptionNotCrash(int i) throws CustomException;
    复制代码
    • Native
    extern "C" JNIEXPORT jstring 
    JNICALL
    Java_com_wsy_jnidemo_MainActivity_testExceptionNotCrash(
            JNIEnv *env,
            jobject /* this */, jint i) {
        jstring hello = env->NewStringUTF("hello world");
        if (i > 100) {
            jclass exceptionCls = env->FindClass("com/wsy/jnidemo/CustomException");
            env->ThrowNew(exceptionCls, "i must <= 100");
            env->DeleteLocalRef(exceptionCls);
        }
        // 若出现异常,则env已不可以使用一些方法,例如建立String将会crash,
        // 文档说明:https://developer.android.google.cn/training/articles/perf-jni#exceptions_1
        return hello;
    }
    复制代码

    这里虽然没出现crash,可是Java层也是没法接收到hello这个String对象的

  • 注意事项

    须要注意的是,在exception发生时,在native层已不能调用大部分的方法,例如对象建立。

    NDK官网说明以下:

    You must not call most JNI functions while an exception is pending. Your code is expected to notice the exception (via the function's return value,ExceptionCheck, or ExceptionOccurred) and return, or clear the exception and handle it. The only JNI functions that you are allowed to call while an exception is pending are:

    • DeleteGlobalRef
    • DeleteLocalRef
    • DeleteWeakGlobalRef
    • ExceptionCheck
    • ExceptionClear
    • ExceptionDescribe
    • ExceptionOccurred
    • MonitorExit
    • PopLocalFrame
    • PushLocalFrame
    • ReleaseArrayElements
    • ReleasePrimitiveArrayCritical
    • ReleaseStringChars
    • ReleaseStringCritical
    • ReleaseStringUTFChars

    例如在抛出exception后执行

    env->NewStringUTF("hello world, after exception");
    复制代码

    将致使crash,就像这样。

    可是咱们刚才说到,当异常发生时,执行ExceptionDescribe会调用异常对应的Java类的printStackTrace()函数,这又是怎么作到的?明明已经出现了异常,为什么还能进行和Java交互? 让咱们看一看源码:

    art/runtime/jni_internal.cc

    static void ExceptionDescribe(JNIEnv* env) {
        ScopedObjectAccess soa(env);
    
        // If we have no exception to describe, pass through.
        if (!soa.Self()->GetException()) {
          return;
        }
    
        StackHandleScope<1> hs(soa.Self());
        Handle<mirror::Throwable> old_exception(
            hs.NewHandle<mirror::Throwable>(soa.Self()->GetException()));
        soa.Self()->ClearException();
        ScopedLocalRef<jthrowable> exception(env,
                                             soa.AddLocalReference<jthrowable>(old_exception.Get()));
        ScopedLocalRef<jclass> exception_class(env, env->GetObjectClass(exception.get()));
        jmethodID mid = env->GetMethodID(exception_class.get(), "printStackTrace", "()V");
        if (mid == nullptr) {
          LOG(WARNING) << "JNI WARNING: no printStackTrace()V in "
                       << mirror::Object::PrettyTypeOf(old_exception.Get());
        } else {
          env->CallVoidMethod(exception.get(), mid);
          if (soa.Self()->IsExceptionPending()) {
            LOG(WARNING) << "JNI WARNING: " << mirror::Object::PrettyTypeOf(soa.Self()->GetException())
                         << " thrown while calling printStackTrace";
            soa.Self()->ClearException();
          }
        }
        soa.Self()->SetException(old_exception.Get());
    }
    复制代码

    这里有个操做:

    1. 把Exception记下来
    2. ExceptionClear
    3. 经过JNI调用异常的printStackTrace()函数(调用过程当中出了异常仍是会clear,稳)
    4. 把Exception放回去

3、crash的处理

哪怕JNI提供了这么些函数,但仍是crash了咋办?

NDK中有一个很方便的debug工具:addr2line,可直接根据指令地址定位代码crash位置。 addr2line的位置在:{NDK安装目录}\toolchains\{平台相关目录}\prebuilt\windows-x86_64\bin目录。 咱们先在native层写个错误示范代码,再运行,能够看到日志中出现了crash信息:

crash信息
找到工程相关的动态库文件,能够看到出现crash的so文件为 /lib/x86/libnative-lib.so以及指令地址为 00000E61,动态库文件在工程目录的位置以下图:
so文件路径
此时使用 addr2line工具进行crash位置查找,用法为:

addr2line -e so文件路径 指令地址
复制代码

addr2line -e D:\android-project\study\JNIDemo\app\build\intermediates\cmake\debug\obj\x86\libnative-lib.so 00000e61
复制代码

运行结果以下

运行addr2line结果

能够看到定位到的致使crash的代码是native-lib.cpp文件的33行:

crashCode



demo可直接clone:

github.com/wangshengya…

详细的JNI使用方法:

developer.android.google.cn/training/ar…

相关文章
相关标签/搜索