最近我在调查Android应用中的崩溃。我找到了原因并修复了它,但是从那时起我就一直在想如何处理它("处理"在捕获它的意义上是为了避免"未处理"异常。)
当我启动一个新活动时发生了崩溃。但是在我的源代码中执行任何可见的代码时都没有。相反,它在我退出代码中的最后一个事件处理程序之后但在显示View之前崩溃了。
我的Activity中有几个事件处理程序,例如onCreate()
,onPause()和onAttachedToWindow()
。在后者我做了一个。 。 。
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
该程序最初是针对API 8构建的,但是当我开始爆炸时切换到API 18时。基于另一个SO问题,我评论说这条线和问题消失了。
当它崩溃时,显示器显示。 。 。
W / dalvikvm:threadid = 1:线程退出未捕获的异常 (group = 0x4136a960)E / AndroidRuntime:FATAL EXCEPTION: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) at 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) at 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);
}
那么我将来可以在哪里设置处理程序来捕获这些类型的崩溃?
... N.B。,通过"这些"崩溃我不是特别指的是" TYPE_KEYGUARD&#34 ;;我的意思是当系统设置或显示之前在我的代码中调用的屏幕/视图时,在我的代码之外发生的崩溃。
换句话说,我不希望用户得到" 不幸的是,您的应用已停止"我写的错误没有通过错误处理程序。我希望能够记录每次崩溃的细节,用户正在做什么,并优雅地关闭连接并告诉用户发生了什么。
答案 0 :(得分:3)
您实现的try catch将无法捕获目标活动中引发的异常,因为新活动将在另一个线程堆栈上启动。
你应该致电
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
在调用setContentView()
之前setContentView(R.layout.yourlayoutfile);
这样可以防止您面临的异常。
答案 1 :(得分: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
)可能弊大于利。在崩溃的情况下,您可以对应用程序的状态做出哪些假设?
你会如何处理错误?也许,通过将用户带到应用程序的登录页面。这可能会比#34更糟糕的接收;不幸的是,应用程序停止了对话"。
更好的解决方案是通过让您的应用保持最新(定位正确的API)和writing instrumented tests并尽可能多地针对尽可能多的设备执行这些错误来尝试找到这些错误。如果您将应用设为Firebase app,则可以针对云中的多个设备进行测试。如果你不能做这样的测试,使用Crashlytics获取堆栈跟踪并及时修复错误可能比尝试实现一些自定义的非标准错误恢复方法更好。
答案 2 :(得分:2)
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();
}
}
答案 3 :(得分:1)
有趣的问题。我可以看到你想要一个更好的用户体验,以应对无法预料的崩溃。我没有试过这个,但请看一下Thread文档中的setUncaughtExceptionHandler。
setUncaughtExceptionHandler
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
设置当此线程由于未捕获的异常而突然终止时调用的处理程序。
答案 4 :(得分:1)
如果您不想使用第三方工具,可以注册自己的处理程序。
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" >
答案 5 :(得分:1)
https://github.com/Ereza/CustomActivityOnCrash
我不确定这是不是你的意思但我认为你想要一种方式,用户可以在发生这种情况时向你发送有用的信息?
我没有写它,但我使用这个库显示了一个自定义崩溃活动,并为他们提供了查看堆栈跟踪的选项,以便他们可以将它发送给我并重启应用程序。不是过于优雅,但比“App停止了”更好。我没有考虑过定制它,但我想你可以用它来解决你的问题并找到代码需要去处理异常的地方。