Android全局异常处理
前言:
在开发中我们会遇到程序崩溃闪退的问题,但是不是每次崩溃闪退都能抓到报错的异常信息,为了方便程序开发调试,定位分析异常发生的原因,我们希望程序在崩溃闪退的时候能够把异常信息记录下来,方便我们后期分析。这时候我们可以用全局异常处理来实现异常信息记录的功能。
代码实现
- 创建异常处理类
CrashHandler
,当程序发生Uncaught异常的时候,由该类来接管程序,并记录错误日志。
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "MyCrash";
// 系统默认的UncaughtException处理类
private UncaughtExceptionHandler mDefaultHandler;
@SuppressLint("StaticFieldLeak")
private static CrashHandler instance = new CrashHandler();
private Context mContext;
// 用于格式化日期,作为日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
/**
* 保证只有一个CrashHandler实例
*/
private CrashHandler() {
}
/**
* 获取CrashHandler实例 ,单例模式
*/
public static CrashHandler getInstance() {
return instance;
}
/**
* 初始化
*/
public void init(Context context) {
mContext = context.getApplicationContext();
// 获取系统默认的UncaughtException处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
SystemClock.sleep(3000);
// 退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*/
private boolean handleException(Throwable ex) {
if (ex == null)
return false;
try {
// 使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程序出现异常,即将返回.",
Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
// 保存日志文件
saveCrashInfoFile(ex);
SystemClock.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 保存错误信息到文件中
*/
private void saveCrashInfoFile(Throwable ex) throws Exception {
StringBuilder sb = new StringBuilder();
try {
SimpleDateFormat sDateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
String date = sDateFormat.format(new Date());
sb.append("\r\n").append(date).append("\n");
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
printWriter.flush();
printWriter.close();
String result = writer.toString();
sb.append(result);
writeFile(sb.toString());
} catch (Exception e) {
Log.e(TAG, "an error occurred while writing file...", e);
sb.append("an error occurred while writing file...\r\n");
writeFile(sb.toString());
}
}
private void writeFile(String sb) throws Exception {
String time = formatter.format(new Date());
String fileName = "crash-" + time + ".txt";
if (hasSdcard()) {
String path = getGlobalPath();
File dir = new File(path);
if (!dir.exists())
dir.mkdirs();
FileOutputStream fos = new FileOutputStream(path + fileName, true);
fos.write(sb.getBytes());
fos.flush();
fos.close();
}
}
// 异常log保存路径
private String getGlobalPath() {
return mContext.getExternalFilesDir("").getAbsolutePath() + "/Crash/";
}
// 判断是否存在sd卡
private boolean hasSdcard() {
return Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED);
}
}
- 自定义一个继承
Application
的全局入口MyApplication
,注册异常处理类。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler.getInstance().init(this);
}
}
- 在
AndroidManifest.xml
文件中注册自定义的Application
。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.test">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name="com.test.exception.handle.MyApplication"
android:allowBackup="false"
android:icon="@mipmap/logo_test"
android:keepScreenOn="true"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo_test"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>