【问题标题】:Catching exceptions thrown from native code running on Android捕获运行在 Android 上的本机代码引发的异常
【发布时间】:2012-01-14 22:44:31
【问题描述】:

我目前正在进行的项目需要我编写跨平台程序实现的 android 部分。

通过android-ndk 构建并包含在我的应用程序中的一组核心功能。我发现本机代码中发生的任何异常/崩溃充其量只是偶尔报告一次。发生错误时,我会出现以下行为之一:

  • 发生堆栈跟踪/内存转储并写入日志文件。程序消失(设备上没有显示应用程序突然消失的原因)。
  • 没有堆栈跟踪/转储或其他指示本机代码已崩溃。程序消失。
  • java 代码以NullPointerException 崩溃(通常每个本地代码异常都在同一个地方,这是一个巨大的痛苦)。通常会导致我花费相当长的时间尝试调试 Java 代码抛出错误的原因,结果却发现 Java 代码很好并且本机代码错误已被完全掩盖。

我似乎找不到任何方法来“隔离”我的代码,使其免受本机代码中出现的错误的影响。 Try/catch 语句被彻底忽略。除了当我的代码被指责为罪魁祸首之外,我什至没有机会警告用户发生错误。

有人能帮我解决一下原生代码崩溃的情况吗?

【问题讨论】:

  • 单元测试,日志......我知道的唯一替代方案(但我知道所有事情都知道 faaar,所以请进一步查看:))
  • 你控制原生代码吗?还是只是 Java 方面?
  • 只有最顶层的原生代码,即JNI Binder层。

标签: android exception-handling java-native-interface android-ndk nullpointerexception


【解决方案1】:

您是否考虑过捕获此异常,然后将其包装在运行时异常中,以使其在堆栈中更高?

我在 SCJD 中使用了类似的“hack”。通常,NPE 表示您的错误,但如果您确信自己没有做错任何事情,那么只需制作一个有据可查的RuntimeException,说明该异常用于冒泡异常。然后打开它并测试是否为 NPE 实例并将其作为您自己的异常处理。

如果它会导致错误的数据,那么你别无选择,只能找到它的根源。

【讨论】:

  • 你的意思是,你认为我应该通过抛出 RuntimeException 来传播异常?在调试应用程序时,当在变量上调用 .set()(例如)时将引发 NPE,您可以通过调试器看到该变量,并且可以在调用 .set() 之前直接从 Log.v() 报告。
  • 我的意思是,如果你无法找到它的根源,因为它不是你自己的 API 抛出的。然后将它自己包装在一个您完全了解的异常中,例如“BubbleException”,然后您可以在堆栈中更高层测试此异常并将其作为您自己的处理。通常,尽管 NPE 表明某处存在空值,但如果您认为它的根在堆栈中较低(可能是您正在使用的代码),那么要么放弃代码,要么将其放入您自己的 RuntimeException 中,以免给您的 API 客户端带来不便有一个你自己无法解释的例外情况。
  • 我的意思是本机代码不会“抛出”异常,它以完全不同的方式死亡,try / catch 无法捕捉到。我认为NPE 的报告是本机代码死亡的副作用,与崩溃没有任何直接关系。
  • 没问题 - 感谢您的尝试。认为这个问题很难确定
  • 另外,请注意之前评论中的“我认为” :)
【解决方案2】:

我曾经遇到过同样的问题,确实在 android 中(在执行本机代码时通常在任何 VM 中)如果你抛出一个 C++ 异常并且这个异常没有被捕获,VM 就会死掉(如果我理解正确的话,我认为这是你的问题)。我采用的解决方案是在 C++ 中捕获任何异常并抛出 java 异常,而不是使用 JNI。下一个代码是我的解决方案的简化示例。首先,您有一个捕获 C++ 异常的 JNI 方法,然后在 try 子句中注释 Java 异常。

JNIEXPORT void JNICALL Java_com_MyClass_foo (JNIEnv *env, jobject o,jstring param)
{
    try
    {
        // Your Stuff
        ...
    }
    // You can catch std::exception for more generic error handling
    catch (MyCxxException e)
    {
        throwJavaException (env, e.what());
    }
}


void throwJavaException(JNIEnv *env, const char *msg)
{
    // You can put your own exception here
    jclass c = env->FindClass("company/com/YourException");

    if (NULL == c)
    {
        //B plan: null pointer ...
        c = env->FindClass("java/lang/NullPointerException");
    }

    env->ThrowNew(c, msg);
}

请注意,在 ThrowNew 之后,本机方法不会突然自动终止。也就是说,控制流返回到您的本机方法,此时新的异常处于未决状态。在您的 JNI 方法完成后将引发异常。

我希望这是您正在寻找的解决方案。

【讨论】:

  • 我希望有更多的东西......包容。但在赏金之后,这似乎与将 Java 代码与 Native 代码崩溃隔离开来的效果一样好。
  • 虽然这个答案非常适合捕获抛出的 C++ 异常,但它不能处理 SIGNAL 错误(包括 C 版本的 NullPointerException)。这是一篇很棒的帖子,通过上面的介绍,它应该能够在您的原生应用程序中严格确定错误报告:stackoverflow.com/a/1789879/726954
  • 这个答案更详细一些,可能更适合您的需求:stackoverflow.com/a/12014833
  • 为什么我们需要 B 计划?如果失败了怎么办?
  • @MaksimDmitriev 计划 B 是为了防止您使用 ow 异常并且它不在类路径上。我已经更改了代码以使其更清晰。
【解决方案3】:

编辑:   另见this more elegant answer


以下机制基于我已在JNI 层中成功实现的C preprocessor macro

上面的宏CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION将C++异常转换成Java异常。

用您自己的 C++ 异常替换 mypackage::Exception。如果你没有在Java中定义对应的my.group.mypackage.Exception,那么将"my/group/mypackage/Exception"替换为"java/lang/RuntimeException"

#define CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION              \
                                                                  \
  catch (const mypackage::Exception& e)                           \
  {                                                               \
    jclass jc = env->FindClass("my/group/mypackage/Exception");   \
    if(jc) env->ThrowNew (jc, e.what());                          \
    /* if null => NoClassDefFoundError already thrown */          \
  }                                                               \
  catch (const std::bad_alloc& e)                                 \
  {                                                               \
    /* OOM exception */                                           \
    jclass jc = env->FindClass("java/lang/OutOfMemoryError");     \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::ios_base::failure& e)                         \
  {                                                               \
    /* IO exception */                                            \
    jclass jc = env->FindClass("java/io/IOException");            \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::exception& e)                                 \
  {                                                               \
    /* unknown exception */                                       \
    jclass jc = env->FindClass("java/lang/Error");                \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (...)                                                     \
  {                                                               \
    /* Oops I missed identifying this exception! */               \
    jclass jc = env->FindClass("java/lang/Error");                \
    if(jc) env->ThrowNew (jc, "unidentified exception");          \
  }

使用上述宏的文件Java_my_group_mypackage_example.cpp

JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    return jlong(result);
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
  return 0;
}

JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    jstring jstr = env->NewStringUTF("my result");
    return  jstr;
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
  return 0;
}

JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
}

仅供参考或好奇,我在下面提供了相应的 Java 代码(文件example.java)。请注意,“my-DLL-name”是将上述 C/C++ 代码编译为 DLL(“my-DLL-name”没有“.dll”扩展名)。这也可以完美地使用 Linux/Unix 共享库 *.so

package my.group.mypackage;

public class Example {
  static {
    System.loadLibrary("my-DLL-name");
  }

  public Example() {
    /* ... */
  }

  private native int    function1(int); //declare DLL functions
  private native String function2(int); //using the keyword
  private native void   function3(int); //'native'

  public void dosomething(int value) {
    int result = function1(value);  
    String str = function2(value);  //call your DLL functions
    function3(value);               //as any other java function
  }
}

首先,从example.java 生成example.class(使用javac 或您最喜欢的IDE 或maven...)。其次,使用javahexample.class生成C/C++头文件Java_my_group_mypackage_example.h

【讨论】:

  • 嗨@itsrajesh4uguys 我认为您的问题就在使用CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION 之前。要本地化该行,您可以将CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION 替换为相应的代码(每行末尾不带``)。祝你好运,干杯;-)
猜你喜欢
  • 2019-03-10
  • 2014-02-20
  • 2012-07-17
  • 2022-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-22
  • 2010-09-14
相关资源
最近更新 更多