Android崩溃异常捕获方法

前端技术 2023/09/06 Android

开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。
那么今天主要讲一下如何去捕捉系统出现的Unchecked异常。何为Unchecked异常呢,换句话说就是指非受检异常,它不能用try-catch来显示捕捉。

我们先从Exception讲起。Exception分为两类:一种是CheckedException,一种是UncheckedException。这两种Exception的区别主要是CheckedException需要用try...catch...显示的捕获,而UncheckedException不需要捕获。通常UncheckedException又叫做RuntimeException。《effective java》指出:对于可恢复的条件使用被检查的异常(CheckedException),对于程序错误(言外之意不可恢复,大错已经酿成)使用运行时异常(RuntimeException)。我们常见的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。对于那些CheckedException就不胜枚举了,我们在编写程序过程中try...catch...捕捉的异常都是CheckedException。io包中的IOException及其子类,这些都是CheckedException。

一、使用UncaughtExceptionHandler来捕获unchecked异常

UncaughtException处理类,当程序发生Uncaught异常的时候,由该类来接管程序,并记录发送错误报告。
直接上代码吧

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
/**
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
* 
* @author user
* 
*/
@SuppressLint(\"SdCardPath\")
public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = \"TEST\";
// CrashHandler 实例
private static CrashHandler INSTANCE = new CrashHandler();
// 程序的 Context 对象
private Context mContext;
// 系统默认的 UncaughtException 处理类
private Thread.UncaughtExceptionHandler mDefaultHandler;
// 用来存储设备信息和异常信息
private Map<String, String> infos = new HashMap<String, String>();
// 用来显示Toast中的信息
private static String error = \"程序错误,额,不对,我应该说,服务器正在维护中,请稍后再试\";
private static final Map<String, String> regexMap = new HashMap<String, String>();
// 用于格式化日期,作为日志文件名的一部分
private DateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\",
Locale.CHINA);
/** 保证只有一个 CrashHandler 实例 */
private CrashHandler() {
//
}
/** 获取 CrashHandler 实例 ,单例模式 */
public static CrashHandler getInstance() {
initMap();
return INSTANCE;
}
/**
* 初始化
* 
* @param context
*/
public void init(Context context) {
mContext = context;
// 获取系统默认的 UncaughtException 处理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// 设置该 CrashHandler 为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
Log.d(\"TEST\", \"Crash:init\");
}
/**
* 当 UncaughtException 发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
Log.d(\"TEST\", \"defalut\");
} else {
try {
Thread.sleep();
} catch (InterruptedException e) {
Log.e(TAG, \"error : \", e);
}
// 退出程序
android.os.Process.killProcess(android.os.Process.myPid());
// mDefaultHandler.uncaughtException(thread, ex);
System.exit();
}
}
/**
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
* 
* @param ex
* @return true:如果处理了该异常信息;否则返回 false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
// 收集设备参数信息
// collectDeviceInfo(mContext);
// 保存日志文件
saveCrashInfoFile(ex);
// 使用 Toast 来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, error, Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
return true;
}
/**
* 收集设备参数信息
* 
* @param ctx
*/
public void collectDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null ? \"null\"
: pi.versionName;
String versionCode = pi.versionCode + \"\";
infos.put(\"versionName\", versionName);
infos.put(\"versionCode\", versionCode);
}
} catch (NameNotFoundException e) {
Log.e(TAG, \"an error occured when collect package info\", e);
}
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
Log.d(TAG, field.getName() + \" : \" + field.get(null));
} catch (Exception e) {
Log.e(TAG, \"an error occured when collect crash info\", e);
}
}
}
/**
* 保存错误信息到文件中 *
* 
* @param ex
* @return 返回文件名称,便于将文件传送到服务器
*/
private String saveCrashInfoFile(Throwable ex) {
StringBuffer sb = getTraceInfo(ex);
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.close();
String result = writer.toString();
sb.append(result);
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = \"crash-\" + time + \"-\" + timestamp + \".log\";
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory()
+ \"/crash/\";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
fos.close();
}
return fileName;
} catch (Exception e) {
Log.e(TAG, \"an error occured while writing file...\", e);
}
return null;
}
/**
* 整理异常信息
* @param e
* @return
*/
public static StringBuffer getTraceInfo(Throwable e) {
StringBuffer sb = new StringBuffer();
Throwable ex = e.getCause() == null ? e : e.getCause();
StackTraceElement[] stacks = ex.getStackTrace();
for (int i = ; i < stacks.length; i++) {
if (i == ) {
setError(ex.toString());
}
sb.append(\"class: \").append(stacks[i].getClassName())
.append(\"; method: \").append(stacks[i].getMethodName())
.append(\"; line: \").append(stacks[i].getLineNumber())
.append(\"; Exception: \").append(ex.toString() + \"\\n\");
}
Log.d(TAG, sb.toString());
return sb;
}
/**
* 设置错误的提示语
* @param e
*/
public static void setError(String e) {
Pattern pattern;
Matcher matcher;
for (Entry<String, String> m : regexMap.entrySet()) {
Log.d(TAG, e+\"key:\" + m.getKey() + \"; value:\" + m.getValue());
pattern = Pattern.compile(m.getKey());
matcher = pattern.matcher(e);
if(matcher.matches()){
error = m.getValue();
break;
}
}
}
/**
* 初始化错误的提示语
*/
private static void initMap() {
// Java.lang.NullPointerException
// java.lang.ClassNotFoundException
// java.lang.ArithmeticException
// java.lang.ArrayIndexOutOfBoundsException
// java.lang.IllegalArgumentException
// java.lang.IllegalAccessException
// SecturityException
// NumberFormatException
// OutOfMemoryError 
// StackOverflowError 
// RuntimeException 
regexMap.put(\".*NullPointerException.*\", \"嘿,无中生有~Boom!\");
regexMap.put(\".*ClassNotFoundException.*\", \"你确定你能找得到它?\");
regexMap.put(\".*ArithmeticException.*\", \"我猜你的数学是体育老师教的,对吧?\");
regexMap.put(\".*ArrayIndexOutOfBoundsException.*\", \"恩,无下限=无节操,请不要跟我搭话\");
regexMap.put(\".*IllegalArgumentException.*\", \"你的出生就是一场错误。\");
regexMap.put(\".*IllegalAccessException.*\", \"很遗憾,你的信用卡账号被冻结了,无权支付\");
regexMap.put(\".*SecturityException.*\", \"死神马上降临\");
regexMap.put(\".*NumberFormatException.*\", \"想要改变一下自己形象?去泰国吧,包你满意\");
regexMap.put(\".*OutOfMemoryError.*\", \"或许你该减减肥了\");
regexMap.put(\".*StackOverflowError.*\", \"啊,啊,憋不住了!\");
regexMap.put(\".*RuntimeException.*\", \"你的人生走错了方向,重来吧\");
}
}

二、建立一个Application来全局监控

import android.app.Application;
public class CrashApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
}
} 

最后在配置文件中加入注册信息

<application android:name=\".CrashApplication\" ... /> 

和权限

<!--uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /-->
<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /> 

提交错误日志到网络服务器这一块还没有添加。如果添加了这一块功能,就能够实时的得要用户使用时的错误日志,能够及时反馈不同机型不同时候发生的错误,能对我们开发者的后期维护带来极大的方便。

有关Android崩溃异常捕获方法小编就给大家介绍这么多,希望对大家有所帮助!

本文地址:https://www.stayed.cn/item/15635

转载请注明出处。

本站部分内容来源于网络,如侵犯到您的权益,请 联系我

我的博客

人生若只如初见,何事秋风悲画扇。