在父活动中捕获子活动的异常

15

我一直在想,是否可以通过在父活动中捕获崩溃来阻止Android应用程序的崩溃。

假设我在子活动的onCreate方法中引发了致命异常,我是否能以任何方式捕获该异常?或者无论我尝试什么,应用程序都会崩溃?

以下是我的一个示例:

Main.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.ly_main);
    // My Main activity starts
    try{
        // Call the next activity
        Intent intent = new Intent(getApplicationContext(), Child.class);
        startActivity(intent);
    }catch(Exception e){
        Log.wtf("Exception_WTF","Exception from child activity woohoo \n "+ e.toString());
    }

Child.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.ly_child);
    // Create exception... for science
    int a = 0;
    a = 1/a;
}

这不起作用。子活动会崩溃并带着父活动一起崩溃。

通过startActivityForResult是否可能实现?

谢谢,

编辑:我不需要崩溃数据,我只想知道如何避免应用程序崩溃。

我找到了以下内容: 在Android上使用全局异常处理

其中包含了这段内容:

   Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
            Log.e("Alert","Lets See if it Works !!!");
        }
    });

我想记录未捕获异常以避免“崩溃”,但是应用程序变成黑屏并停止响应...

编辑2: 经过大量阅读(感谢user370305)在线程如何从我的Android应用程序中获取崩溃数据?

我陷入了僵局,要么处理未捕获的异常并调用defaultUEH.uncaughtException(paramThread,paramThrowable)让应用程序崩溃,要么不调用defaultUEH.uncaughtException,应用程序既不会崩溃也无法响应...有任何想法吗?

final Thread.UncaughtExceptionHandler defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
        Log.e("Alert","Lets See if it Works !!!");
        defaultUEH.uncaughtException(paramThread, paramThrowable);
    });

是的,这是可能的,异常是可序列化的,因此它们可以放在一个bundle中。 - Tobrun
1
活动是独立的,意图通信是异步的。也许你可以探索startActivityForResult的方式。 - lithos35
1
请查看https://dev59.com/kXRB5IYBdhLWcg3wgXdV。 - user370305
StartActivity 顺便说一下没有起作用 - RdPC
你可以通过一些创意做到这一点。给我几天时间,我会发布一些代码的(很忙碌)。 - Luis
7个回答

6
希望我理解您的要求正确。您可以查看FBReaderJ的源代码,这是一个处理您的问题的真实世界示例。他们能够很好地处理它:每当应用程序出错时,他们将向用户显示错误报告对话框,并通过读取Throwable信息来过滤错误。
例如,请查看BookInfoActivity,他们像这样注册了异常处理程序:
Thread.setDefaultUncaughtExceptionHandler(
        new org.geometerplus.zlibrary.ui.android.library.UncaughtExceptionHandler(this)
    );

接下来,UncaughtExceptionHandler类会处理错误:

  @Override
public void uncaughtException(Thread thread, Throwable exception) {
    final StringWriter stackTrace = new StringWriter();
    exception.printStackTrace(new PrintWriter(stackTrace));
    System.err.println(stackTrace);

    if(myContext != null && myContext instanceof Activity){
        ((Activity)myContext).finish();
        return;
    }


    // filter the error by get throwable class name and put them into 
    // intent action which registered in manifest with particular handle activity.
    Intent intent = new Intent(
        "android.fbreader.action.CRASH",
        new Uri.Builder().scheme(exception.getClass().getSimpleName()).build()
    );
    try {
        myContext.startActivity(intent);
    } catch (ActivityNotFoundException e) {
    // or just go to the handle bug activity
        intent = new Intent(myContext, BugReportActivity.class);
        intent.putExtra(BugReportActivity.STACKTRACE, stackTrace.toString());
        myContext.startActivity(intent);
    }

    if (myContext instanceof Activity) {
        ((Activity)myContext).finish();
    }

        // kill the error thread
        Process.killProcess(Process.myPid());
    System.exit(10);
}

希望这能有所帮助。


1
是的,这看起来像是我正在寻找的东西,因为我不在我的开发人员电脑附近,所以我无法测试它,但时间正在紧迫,我认为你对问题的新视角值得奖励。 如果解决方案有效,我将在星期一检查并将其设置为接受的答案。感谢您提供的示例和信息! - RdPC
@RdPC 还没到周一吗? ;) 我测试了这个解决方案,但不幸的是它不能防止主活动崩溃。你可以启动一个新活动,但无法返回到“主活动”,因为它在被杀死的线程中,所以它一直死亡。如果有一种方法不让调用活动被杀死就太好了! - QuickFix
@QuickFix 我认为没有办法做到这一点。因为当您的活动出现异常并且应该崩溃时,会调用此异常处理程序。您可以通过正确编写所有代码并不创建任何异常来避免这种情况。或者,您可以重新启动MainActivity(创建新实例)并恢复先前的数据。 - ductran

6

由于 Android 中的活动具有 自己的生命周期,因此必须独立管理。因此,仅在产生异常的活动中捕获异常

如果您的活动需要与前一个用户活动进行交互才能返回,请结束正在捕获异常的活动(子活动),并让前一个活动(父活动)了解结果。请参见 启动活动和获取结果 ,作为实现父活动和子活动之间通信的方法。


我知道应该在产生异常的活动中捕获异常,也知道如何从子活动中获取结果,问题不在于编程是否正确,而是由于我可以覆盖UncaughtExceptionHandler,所以我想知道能否对我收到的未捕获异常做些什么... - RdPC

1

崩溃需要来自代码的某个特定点。您可以在子代中使用if语句过滤导致崩溃的条件,然后抛出异常。父代将在调用其子代时使用try {} catch {}语句处理异常。请参见此处。因此,对于您的代码,我猜子代看起来像

import java.io.TantrumException;

public class Child throws TantrumException() {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.ly_child);
    // Create exception... for science
    int a = 0;
    if (a == 0)
         throw new TantrumException("Arrrggghhh!");
    a = 1/a;
    }
}

抱歉,忍不住要这样做了:)
说真的,捕获全局异常听起来有点危险,因为你不知道它会从哪里传递,但事先已经决定如何处理它,并且根据您的代码,无论调用子程序如何,都会终止。

异常会沿着函数和过程的堆栈向上传递,直到它们到达堆栈的末尾或被catch捕获,然而,这在活动中并非如此:p 因为Android将销毁子活动并导致父活动,无论您在父活动中放置try catch的位置(也尝试在onResume中)。 - RdPC

1
我认为你误解了异常的本质。你只能捕获来自你代码中的方法调用所引发的异常。换句话说,你只能捕获调用栈中在你代码下面的调用。请看下面的调用栈:
main@830028322576, prio=5, in group 'main', status: 'RUNNING'
  at com.stuff.ui.MainActivity.onCreate(MainActivity.java:83)
  at android.app.Activity.performCreate(Activity.java:5372)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1104)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2257)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2349)
  at android.app.ActivityThread.access$700(ActivityThread.java:159)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1316)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:5419)
  at java.lang.reflect.Method.invokeNative(Method.java:-1)
  at java.lang.reflect.Method.invoke(Method.java:525)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
  at dalvik.system.NativeStart.main(NativeStart.java:-1)

这只是我应用程序中某个活动的onCreate方法中调用堆栈的一个例子。如您所见,第一行以下的所有代码都属于androidcom.androidjavadalvik包。重要的是,在第一行后没有用户代码在此调用堆栈中。这意味着,除了onCreate方法本身以外,无法在任何地方捕获任何异常。我希望这可以让你清楚了解在另一个活动中捕获异常。
如果您想知道子活动未能成功创建自己,您应该在child活动的onCreate方法中捕获所有异常,然后使用一些标准的Android机制通知父活动。 startActivityForResult可能是其中之一。

我确实理解异常的工作原理,我只是想知道是否有一种方法可以在父Activity上获取它,因为我能够修改父Activity上的DefaultUncaughtExceptionHandler并记录子异常,当然我知道如果通过startActivityForResult调用活动,我可以在子活动中捕获它并发送结果,但如何编写正确的程序不是重点。我想知道是否可能... - RdPC
你的问题标题表明你想要捕获那些异常,而我的观点是这是不可能的,以及为什么会这样。如果你稍微思考一下,捕获和获取异常是完全不同的事情。我的第二个观点是你最好选择平台推荐的方法,而不是一些hack或变通方法。这将使你的开发在长期内更加容易。 - Haspemulator

1
您可以在主活动意图的包中将作为额外参数传递的ResultReceiver用于子活动意图。以下是从主活动启动子活动的方法:
private void startChildActivity(){
    Intent childActivityIntent = new Intent(this, ChildActivity.class);
    ResultReceiver rr = new ResultReceiver(mainUiHandler){
        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);
            switch(resultCode){
            case RESULT_ERROR:
                String errorMessage = resultData.getString(RESULT_KEY_ERROR_MESSAGE);
                textView.setText(errorMessage);
                break;
            default:break;
            }
        }
    };

    childActivityIntent.putExtra(INTENT_EXTRA_KEY_RESULT_RECEIVER, rr);

    startActivity(childActivityIntent);
}

这是子活动的代码。

public class ChildActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_child);
        try{
            int a = 0;
            a = 1/a;
        } catch(Exception e){
            Log.e("Exception", e.getMessage());
            Intent startIntent = getIntent();
            ResultReceiver rr = startIntent.getParcelableExtra(MainActivity.INTENT_EXTRA_KEY_RESULT_RECEIVER);
            Bundle resultData = new Bundle();
            resultData.putString(MainActivity.RESULT_KEY_ERROR_MESSAGE, e.getMessage());
            rr.send(MainActivity.RESULT_ERROR, resultData);
            finish(); //close the child activity
        }
    }
}

虽然与使用onActivityResult的方法不同,但您的示例只是在子活动中捕获异常并将反馈发送回父级的另一种方式。我的问题是要求一种处理由子级在父级活动中引起的异常的方法。 - RdPC
好的,我想你可以将该机制封装在自定义活动类中,并抛出一些ChildActivityException……这基本上就像在客户端/服务器情况下所做的那样。我不确定是否有更直接的方法。 - Thomas Philipakis

0

如果您捕获所有可能的异常并提供适当的操作,就可以避免崩溃。

关于在父类中捕获异常:为什么要这样做?您可以在子类中捕获异常,并通过意图将必要的信息发送到父活动。我认为不可能在父类中捕获子类的异常。


是的,我知道如何在子程序中捕获所有异常以防止崩溃,但这不是重点。我只是想知道是否有任何方法可以处理父活动中的异常... - RdPC

0

你做不到。当你遇到UncaughtException时,意味着线程正在终止,你对此无能为力。但是你可以使用UncaughtExceptionHandler来记录此事件并重新启动你的应用程序(并打开“新”的父活动)。Android为所有活动和服务使用单个主线程。因此,如果有人在主线程中抛出未捕获的异常- 所有人都会死亡。你不能在单独的线程中运行活动。


是的,这正是我想到的。我可以捕获未捕获的异常,但如果我不通过再次抛出它来崩溃应用程序,它将停止工作。 - RdPC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接