在项目中,如果有异常未捕获,运行时触发异常,则会将异常抛出,给用户一个不好的体验。
虽然现在系统厂商都对未捕获异常进行了处理,基本不会发生弹出异常,崩溃退出应用的场景,但是我们在项目中一般还是会进行一个全局异常处理,可以自己定义或者使用第三方统计,如有盟统计,腾讯bugly等。
异常定义及分类
异常是指在程序运行过程中所出现的错误,这些错误会干扰到指令的正常执行,从而导致程序异常退出。
对java语言来说,所有异常都继承自Throwable
Error异常是我们无法处理的异常,属于系统异常。
Exception异常是由于我们代码编写失误造成的,可以通过修改代码进行控制。我们所处理的异常都属于此类异常。
异常根据触发时机不同又可分为两种:
编译时异常
如ClassNotFoundException/CanNotFoundId layout未找到id
运行时异常
如数组越界/类型转换异常
对异常的全局处理
/**
* Interface for handlers invoked when a <tt>Thread</tt> abruptly
* terminates due to an uncaught exception.
* 当一个线程因为未捕获异常突然终止时调用
*/
public interface UncaughtExceptionHandler {
/**
*未捕获异常处理
*/
void uncaughtException(Thread t, Throwable e);
}
所以我们只需要实现UncaughtExceptionHandler中uncaughtException方法即可。
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler instance;
private Context mcontext;
private Thread.UncaughtExceptionHandler mDefualtHandller;
private Map<String, String> eMaps = new ArrayMap<>();
private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private boolean isPrintLog;
private CrashHandler() {
}
public static CrashHandler getInstance() {
if (instance == null) {
synchronized (CrashHandler.class) {
if (instance == null) {
instance = new CrashHandler();
}
}
}
return instance;
}
/**
* 初始化
* @param context
* @param isPrintLog 是否输出错误信息
*/
public void init(Context context, boolean isPrintLog) {
mcontext = context;
this.isPrintLog = isPrintLog;
mDefualtHandller = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 当触发未捕获异常时,系统将调用此方法
* @param t
* @param e
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!handleException(e)) {
if (mDefualtHandller != null)
mDefualtHandller.uncaughtException(t, e);
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Process.killProcess(Process.myPid());
System.exit(1);
}
}
/**
* 是否手动处理未捕获异常
*
* @param e
* @return
*/
private boolean handleException(Throwable e) {
if (e == null)
return false;
showExceptionMsg(e);
collectExceptionInfo();
saveExceptionInfo(e);
return true;
}
/**
* 收集异常信息
*/
private void collectExceptionInfo() {
PackageManager pm = mcontext.getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(mcontext.getPackageName(), PackageManager.GET_ACTIVITIES);
if (packageInfo != null) {
eMaps.put("versionName", packageInfo.versionName);
eMaps.put("versionCode", String.valueOf(packageInfo.versionCode));
}
saveDeviceInfo();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/**
* 保存设备信息
*/
private void saveDeviceInfo() {
Field[] fields = Build.class.getFields();
if (fields != null && fields.length > 0)
for (Field field : fields) {
field.setAccessible(true);
try {
eMaps.put(field.getName(), field.get(null).toString());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 保存异常信息到sd卡
*/
private void saveExceptionInfo(Throwable e) {
StringBuffer stringBuffer = new StringBuffer();
for (Map.Entry<String, String> entry : eMaps.entrySet()) {
stringBuffer.append(entry.getKey() + "===" + entry.getValue() + "\n\r");
}
Writer write = new StringWriter();
PrintWriter printer = new PrintWriter(write);
e.printStackTrace(printer);
Throwable cause = e.getCause();
while (cause != null) {
cause.printStackTrace(printer);
e.getCause();
}
printer.close();
stringBuffer.append(write.toString());
long time = System.currentTimeMillis();
String date = dateFormat.format(new Date());
String fileName = "crash-" + date + "-" + time + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String filePath = "/sdcard/" + mcontext.getPackageName() + "-crash/";
File file = new File(filePath);
if (!file.exists())
file.mkdirs();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file + fileName);
fos.write(stringBuffer.toString().getBytes());
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e1) {
}
}
}
//TODO 将错误信息上次到服务器
}
/**
* 提示异常信息
*/
private void showExceptionMsg(Throwable e) {
if (!isPrintLog) return;
if (Looper.getMainLooper() == Looper.myLooper())
showToast(e, true);
else
showToast(e, false);
}
private void showToast(Throwable e, boolean isMainThread) {
if (isMainThread)
Toast.makeText(mcontext, "未捕获异常:" + e.getMessage(), Toast.LENGTH_LONG).show();
else {
Looper.prepare();
Toast.makeText(mcontext, "未捕获异常:" + e.getMessage(), Toast.LENGTH_LONG).show();
Looper.loop();
}
}
}
然后在Application中初始化即可。
public class App extends Application {
CrashHandler crashHandler;
@Override
public void onCreate() {
super.onCreate();
crashHandler=CrashHandler.getInstance();
crashHandler.init(this,BuildConfig.ISDEBUG);
}
}
其中BuildConfig.ISDEBUG可以通过在build.gradle中配置
debug{ buildConfigField('boolean' , 'ISDEBUG', 'true') } release { buildConfigField('boolean' , 'ISDEBUG', 'false') minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }
如果是字符串则需要 buildConfigField(‘String’ , ‘aaaa’, ‘\”fadfasfa\”’)
使用第三方统计
使用有盟统计或者腾讯bugly,按照文档设置完成之后,可以在后台看到异常报表