在应用开发阶段,当程序发生崩溃的时候,我们可以根据打印的错误日志来定位,分析,解决错误。但是当应用发布后,用户在使用的时候因为各种原因导致崩溃,这是非常影响用户体验的。这种情况改下,开发人员无法知道应用是否发生了崩溃,更无法知道是什么地方,因为什么原因发生了崩溃。为了解决这个问题,我们就需要获取应用崩溃时的异常信息进行分析。目前市面上已经有成熟的异常上报分析平台,比如腾讯的 Bugly 等。综上,实现异常上报是开发中必不可少的一个环节,是简单的实现异常上报还是接入成熟的平台就看具体项目的规模了。

Android的两种崩溃

Java 代码崩溃

Native 代码崩溃

本文只分析处理Java代码崩溃

Java的异常类

unCheckedException(非检查异常):程序编译时,编译器不会提示的异常,通常是 Error 和 RuntimeException 以及他们各自的子类。

checkedException(检查异常):除了非检查异常之外的异常,在编译时不进行 try catch 处理或将该异常 throw,编译器会提示出错并无法进行编译。

UncaughtExceptionHandler接口

如果给一个线程设置了 UncaughtExceptionHandler 接口对象

该线程中,所有未处理或者说未捕获的异常都将会由这个接口对象的 void uncaughtException(Thread t, Throwable e) 方法处理。

该线程中抛出异常时,Java 虚拟机将会忽略,也就是说,Java 虚拟机不会让程序崩溃了。

如果没有设置,那么最终会调用 getDefaultUncaughtExceptionHandler 获取默认的 UncaughtExceptionHandler 来处理异常。

Android 程序是运行在 UI 线程中的,我们又会在程序中创建各种子线程。所以给每个线程都通过 setUncaughtExceptionHandler() 这个方法来设置 UncaughtExceptionHandler 不是一个好办法。为了统一处理未捕获异常,我们只需要在应用程序打开的时候,通过 setDefaultUncaughtExceptionHandler() 设置一个默认的 UncaughtExceptionHandler。

UncaughtExceptionHandler实现类
import android.content.Context;
import android.content.Intent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class AppCrashHandler implements Thread.UncaughtExceptionHandler {
private Context mAppContext;
private volatile boolean isCrashing;
private DateFormat mFormatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
private Thread.UncaughtExceptionHandler mDefaultHandler;
private static AppCrashHandler mAppCrashHandler;
public static synchronized AppCrashHandler getInstance() {
if (mAppCrashHandler == null) {
synchronized (AppCrashHandler.class) {
if (mAppCrashHandler == null) {
mAppCrashHandler = new AppCrashHandler();
}
}
}
return mAppCrashHandler;
}
/**
* 初始化
*/
public void init(Context context) {
isCrashing = false;
mAppContext = context.getApplicationContext();
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 处理异常逻辑
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (isCrashing) {
return;
}
isCrashing = true;
e.printStackTrace();
if (!handleException(e) && mDefaultHandler != null) {
mDefaultHandler.uncaughtException(t, e);
}
kill();
}
/**
* 异常退出应用程序
*/
private void kill() {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
/**
* 是否处理异常
*/
private boolean handleException(Throwable e) {
if (e == null) {
return false;
}
try {
String message = getExceptionMessage(e);
save2SDCard(message);
startCrashActivity();
} catch (Exception ex) {
return false;
}
return true;
}
/**
* 获取异常信息
*/
private String getExceptionMessage(Throwable e) {
PrintWriter pw = null;
Writer writer = new StringWriter();
try {
pw = new PrintWriter(writer);
e.printStackTrace(pw);
} catch (Exception ex) {
return "Get exception message fail";
} finally {
if (pw != null) {
pw.close();
}
}
return writer.toString();
}
/**
* 将异常信息写入SD卡
*/
private void save2SDCard(String message) {
try {
String exceptionTime = mFormatter.format(new Date());
if (SDCardUtils.isSDCardEnable()) {
String logPath = FileConstant.LOG_FOLDER;
File dir = new File(logPath);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(logPath + exceptionTime + ".txt");
fos.write(message.getBytes());
fos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 前往错误提醒界面
*/
private void startCrashActivity() {
Intent intent = new Intent();
intent.setClass(mAppContext, CrashActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mAppContext.startActivity(intent);
}
}
我调试的是一个固定在墙面上的终端设备,所以demo中只做了异常日志的本地存储,上传服务器逻辑需要自己实现。另外注意日志文件的命名规范,否则会报错 FileNotFoundException 即使你的文件夹路径存在。
在Application中进行初始化
AppCrashHandler.getInstance().init(getApplicationContext());
异常提示Activity
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class CrashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash);
showDialog();
}
private void showDialog() {
new AlertDialog.Builder(this, R.style.NormalDialogStyle)
.setTitle("某应用")
.setMessage("应用程序异常退出,请联系管理员查看日志").setCancelable(false)
.setNegativeButton("退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
})
.setPositiveButton("重启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
Intent intent = new Intent();
intent.setClass(CrashActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
}
}).show();
}
}