【问题标题】:How do I handle an exception like this?我该如何处理这样的异常?
【发布时间】:2023-03-18 22:21:01
【问题描述】:

最近我正在调查一个 Android 应用程序的崩溃。我找到了原因并修复了它,但从那时起我一直想知道如何处理它(“处理”是指捕获它以避免“未处理”异常。)

当我启动一个新的 Activity 时发生了崩溃。但这不是在执行我的源代码中对我可见的任何代码时。相反,它在我退出代码中的最后一个事件处理程序后但在视图显示之前崩溃了。

我的 Activity 中有几个事件处理程序,例如 onCreate()、onPause() 和 onAttachedToWindow()。在后者中,我做了一个 . . .

this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);

该程序最初是针对 API 8 构建的,但是当我切换到 API 18 时,它就开始崩溃了。基于另一个 SO 问题,我注释掉了该行,问题就消失了。

当它崩溃时,显示器显示 . . .

W/dalvikvm: threadid=1: 线程以未捕获的异常退出 (group=0x4136a960) E/AndroidRuntime: 致命异常: main java.lang.IllegalArgumentException:添加窗口后无法更改窗口类型。 在 android.os.Parcel.readException(Parcel.java:1429) 在 android.os.Parcel.readException(Parcel.java:1379) 在 android.view.IWindowSession$Stub$Proxy.relayout(IWindowSession.java:860) 在 android.view.ViewRootImpl.relayoutWindow(ViewRootImpl.java:4755) 在 android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1661) 在 android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1236) 在 android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5160) 在 android.view.Choreographer$CallbackRecord.run(Choreographer.java:791) 在 android.view.Choreographer.doCallbacks(Choreographer.java:591) 在 android.view.Choreographer.doFrame(Choreographer.java:561) 在 android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:777) 在 android.os.Handler.handleCallback(Handler.java:725) 在 android.os.Handler.dispatchMessage(Handler.java:92) 在 android.os.Looper.loop(Looper.java:176) 在 android.app.ActivityThread.main(ActivityThread.java:5365) 在 java.lang.reflect.Method.invokeNative(Native Method) 在 java.lang.reflect.Method.invoke(Method.java:511) 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869) 在 dalvik.system.NativeStart.main(Native Method) I/Process: 发送信号。 PID: 23536 SIG: 9 断开连接 目标虚拟机,地址:'localhost:8600',传输:'socket'

我的 startActvity() 已经被包裹在一个 try/catch 中,但它没有在那里着陆。 . .

  Intent svc = new Intent(ctx, RegisterActivity.class);
  svc.putExtra("Projectors2Register", params);
  try {
      ctx.startActivity(svc);
  }
  catch (Exception e) {
      Log.e("ShowButtons(normal)Reg", "Exception" + e);
  }

那么我可以在哪里放置一个处理程序来在将来捕获这些类型的崩溃?

...注意,我所说的“这些”崩溃并不是特指“TYPE_KEYGUARD”;我的意思是当系统设置或显示之前在我的代码内部调用的屏幕/视图时发生在我的代码之外的崩溃。

换句话说,我不希望用户在不通过我编写的错误处理程序的情况下得到“不幸的是你的应用程序已停止”错误。我希望能够记录每次崩溃的详细信息、用户在做什么,并优雅地关闭连接并告诉用户发生了什么。

【问题讨论】:

  • setType 行是否包含在它自己的 try-catch 块中?
  • 这是一个可能不应该被“处理”的异常,因为它指向一个编程错误。以某种方式隐藏它只会使修复它的诊断复杂化。
  • _setType 行是否包含在它自己的 try-catch 块中? _ 是的。正如我在 OP 中所说,在离开我的代码后会发生崩溃 - 所有事件处理程序都正常退出,而实际的崩溃会在 Android 尝试建立 View 时发生。
  • @Henry 这是一个可能不应该“处理”的异常,因为它指向一个编程错误。以某种方式隐藏它只会使修复它的诊断复杂化。我不同意。处理它允许程序员记录有关崩溃和用户正在做什么的信息,以帮助诊断。否则用户只会收到“不幸的是,您的应用已停止”,那么您如何诊断呢?
  • @user316117 用户根本不应该得到它。不应该发布带​​有此类错误的代码。 “处理”异常不仅仅是记录它:您必须在运行时恢复,以防出现真正的运行时错误。或修复导致它的编码问题,如本例所示。

标签: android exception-handling


【解决方案1】:

您实现的 try catch 将无法捕获目标活动中引发的异常,因为新活动将在另一个线程堆栈上启动。

你应该打电话

this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);

在调用 setContentView() 之前

setContentView(R.layout.yourlayoutfile);

这将防止您面临的异常。

【讨论】:

  • 谢谢,但我的QUESTION不是关于如何防止崩溃。这是如何处理它。我不希望用户收到 “不幸的是应用程序已停止” 消息,而我无法在崩溃发生时记录有关崩溃的信息。
  • 试试 { this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD); } catch (Exception e) { Log.e("ShowButtons(normal)Reg", "Exception" + e); }
  • 我的 getWindow*().setType()... 已经包含在 try/catch 中。我已经对我的问题进行了澄清。 TYPE_KEYGUARD 只是一个示例。 Android 有一大类错误,崩溃发生在程序员的代码之外,产生“不幸的是您的应用程序已停止” 消息。我不想要“未处理”的异常。可以将其想象为将整个应用程序包装在 try/catch 中。
【解决方案2】:

如果您只是想从用户那里获取崩溃数据(日志、堆栈跟踪等),那么这个问题的任何答案都可以:

How do I obtain crash-data from my Android application?

可以找到最流行的崩溃报告工具here,例如,您可以看到 Crashlytics 目前在列表中排名第二。

此外,您可能已经自动获得了由用户手动报告的崩溃报告,或者来自拥有选择共享使用和诊断数据的手机的用户的报告。

很高兴您希望防止您的用户有糟糕的体验,但我想知道对于可以并且应该捕获的异常类型以及不可以捕获的异常类型是否存在一些误解。

对于抛出检查异常的方法,您别无选择,只能用 try/catch 块包围调用:

File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DOWNLOADS), filename);
try {
    file.createNewFile();
} catch (IOException e) {
    //recover from the exception here if possible
}

此外,您有理由怀疑某些调用可能会失败。例如,从不受信任的来源解析DateTime

String text = editText.getText().toString();
try {
    dateTime = dateTimeParser.parse(text);
}
catch (IllegalArgumentException e) {
    //try and recover here
}

但是,如果您仔细检查文档以了解对 Android SDK 的调用,并且没有提及您需要处理的异常类型,则无需使用 try/catch 块包围该代码:

try { //don't surround this code with a try/catch block
    ctx.startActivity(svc);
}
catch (Exception e) {
    Log.e("ShowButtons(normal)Reg", "Exception" + e);
}

对于这种类型的代码,如果您正确设置了活动,则没有理由怀疑调用会失败,即使失败,您也没有可行的恢复方法。

此外,尝试从此类错误中恢复(例如,通过使用某种 UI 实现 UncaughExceptionHandler)可能弊大于利。在应用程序崩溃的情况下,您可以对应用程序的状态做出哪些假设?

您将如何处理错误?也许,通过将用户带到应用程序的登录页面。这可能比“不幸的是应用程序已停止对话框”更糟糕。

更好的解决方案是通过使您的应用保持最新(以正确的 API 为目标)和writing instrumented tests 并在尽可能多的设备上执行它们来尝试在此类错误发生之前找到它们。如果您将应用设置为Firebase app,则可以针对云中的许多设备设置测试。如果您无法进行此类测试,则使用 Crashlytics 获取堆栈跟踪并及时修复错误可能比尝试实施一些自定义的非标准错误恢复方法更好。

【讨论】:

    【解决方案3】:

    FireCrasher 是一个很好的库,可以帮助您在应用崩溃时恢复。 LINK

    要求:最低 SDK 版本 14

    您可以在应用崩溃时执行您的逻辑。

    public class App extends Application {
    @Override
    public void onCreate() {
        FireCrasher.install(this, new CrashListener() {
    
            @Override
            public void onCrash(Throwable throwable, final Activity activity) {
    
                // show your own message
                Toast.makeText(activity, throwable.getMessage(), Toast.LENGTH_SHORT).show();
    
                // start the recovering process
                recover(activity);
    
                //you need to add your crash reporting tool here
                //Ex: Crashlytics.logException(throwable);
            }
        });
        super.onCreate();
        }
      }
    

    【讨论】:

      【解决方案4】:

      有趣的问题。我可以看到您在面对不可预见的崩溃时如何想要更好的用户体验。我还没有尝试过,但请查看 Thread 文档中的setUncaughtExceptionHandler

      setUncaughtExceptionHandler

      void setUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh)

      设置当该线程由于未捕获的异常而突然终止时调用的处理程序。

      【讨论】:

        【解决方案5】:

        https://github.com/Ereza/CustomActivityOnCrash

        我不确定这是否是你的意思,但我认为你想要一种方式,让用户可以向你发送有用的信息,以防万一他们发生这种情况?

        我没有编写它,但我使用了这个库,它显示了一个自定义崩溃活动,并为他们提供了查看堆栈跟踪的选项,以便他们可以将其发送回给我并重新启动应用程序。并不过分优雅,但比“应用程序已停止”更好。我还没有考虑定制它,但我想你可以用它来解决你的问题并找到代码需要去哪里来处理异常。

        【讨论】:

          【解决方案6】:

          如果您不想使用 3rdparty 工具,可以注册自己的处理程序。

          import android.app.Application;
          import android.util.Log;
          
          import java.io.File;
          import java.io.FileWriter;
          import java.io.IOException;
          import java.text.DateFormat;
          import java.text.SimpleDateFormat;
          import java.util.Date;
          import java.util.Locale;
          
          /**
           * Application class writing unexpected exceptions to a crash file before crashing.
           */
          public class MyApplication extends Application {
              private static final String TAG = "ExceptionHandler";
          
              @Override
              public void onCreate() {
                  super.onCreate();
          
                  // Setup handler for uncaught exceptions.
                  final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
                  Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                      @Override
                      public void uncaughtException(Thread thread, Throwable e) {
                          try {
                              handleUncaughtException(e);
                              System.exit(1);
                          } catch (Throwable e2) {
                              Log.e(TAG, "Exception in custom exception handler", e2);
                              defaultHandler.uncaughtException(thread, e);
                          }
                      }
                  });
              }
          
              private void handleUncaughtException(Throwable e) throws IOException {
                  Log.e(TAG, "Uncaught exception logged to local file", e);
          
                  // Create a new unique file
                  final DateFormat dateFormat =  new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);
                  String timestamp;
                  File file = null;
                  while (file == null || file.exists()) {
                      timestamp = dateFormat.format(new Date());
                      file = new File(getFilesDir(), "crashLog_" + timestamp + ".txt");
                  }
                  Log.i(TAG, "Trying to create log file " + file.getPath());
                  file.createNewFile();
          
                  // Write the stacktrace to the file
                  FileWriter writer = null;
                  try {
                      writer = new FileWriter(file, true);
                      for (StackTraceElement element : e.getStackTrace()) {
                          writer.write(element.toString());
                      }
                  } finally {
                      if (writer != null) writer.close();
                  }
          
                  // You can (and probably should) also display a dialog to notify the user
              }
          }
          

          然后在你的 AndroidManifest.xml 中注册这个 Application 类:

          <application android:name="de.ioxp.arkmobile.MyApplication" >
          

          【讨论】:

            猜你喜欢
            • 2011-09-21
            • 1970-01-01
            • 2020-09-01
            • 2021-04-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多