Android app如果出现了崩溃现象,系统的“程序异常退出”,给应用的用户体验造成不良影响。为了捕获应用运行时异常并给出友好提示,便可继承 UncaughtExceptionHandler 类来处理。
(当然你也可以用友盟,不仅可以帮你记录异常日志,还可以帮你做很多统计信息)
开发者就可以及时获取在该设备上导致崩溃的信息,这对于下一个版本的bug修复帮助极大,所以今天就来介绍一下“游戏蜂窝”如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。
Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
下面是“游戏蜂窝”的异常处理类
package com.cyjh.gundam.application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import com.umeng.message.MsgConstant;
import com.umeng.socialize.common.SocializeConstants;
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.Map;
import java.util.Map.Entry;
public class CrashHandler implements UncaughtExceptionHandler {
CrashHandler实例
public static final String TAG = "CrashHandler";
//用于格式化日期,作为日志文件名的一部分
//用来存储设备信息和异常信息
//程序的Context对象
//系统默认的UncaughtException处理类
/** 保证只有一个CrashHandler实例 */
private CrashHandler() {
}
/** 获取CrashHandler实例 ,单例模式 */
public static CrashHandler getInstance() {
return INSTANCE;
}
初始化
public void init(Context context) {
this.mContext = context;
//获取系统默认的UncaughtException处理器
this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//设置该CrashHandler为程序的默认处理器
Thread.setDefaultUncaughtExceptionHandler(this);
}
//
当UncaughtException发生时会转入该函数来处理
public void uncaughtException(Thread thread, Throwable ex) {
if (handleException(ex) || this.mDefaultHandler == null) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e(TAG, "error : ", e);
}
//退出程序
Process.killProcess(Process.myPid());
System.exit(1);
} else {
this.mDefaultHandler.uncaughtException(thread, ex);
}
ex.printStackTrace();
}
自定义错误处理,收集错误信息 发送错误报告等操作均在此完成
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
//使用1类(其实就是CrashHandler$1 内部类)线程来显示异常信息
(普通做法是
使用Toast来显示异常信息
)
new 1(this).start(); //
collectDeviceInfo(this.mContext); //手机设备信息
saveCrashInfo2File(ex); //保存异常信息到文件
ex.printStackTrace();
return true;
}
//手机设备信息方法
public void collectDeviceInfo(Context ctx) {
try {
PackageInfo pi = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 1);
if (pi != null) {
String versionCode = pi.versionCode + "";
this.infos.put("versionName", pi.versionName == null ? "null" : pi.versionName);
this.infos.put("versionCode", versionCode);
}
} catch (NameNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "an error occured when collect package info", e);
}
for (Field field : Build.class.getDeclaredFields()) {
try {
field.setAccessible(true);
this.infos.put(field.getName(), field.get(null).toString());
Log.d(TAG, field.getName() + " : " + field.get(null));
} catch (Exception e2) {
e2.printStackTrace();
Log.e(TAG, "an error occured when collect crash info", e2);
}
}
}
//保存异常信息到文件方法
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb = new StringBuffer();
for (Entry<String, String> entry : this.infos.entrySet()) {
String value = (String) entry.getValue();
sb.append(((String) entry.getKey()) + "=" + value + "\n");
}
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
ex.printStackTrace(printWriter);
for (Throwable cause = ex.getCause(); cause != null; cause = cause.getCause()) {
cause.printStackTrace(printWriter);
}
printWriter.close();
sb.append(writer.toString());
try {
String fileName = "crash-" + this.formatter.format(new Date()) + SocializeConstants.OP_DIVIDER_MINUS + System.currentTimeMillis() + MsgConstant.CACHE_LOG_FILE_EXT;
String path = BaseApplication.getInstance().getErrorLogPath(this.mContext, fileName);
System.out.println("错误日志保存路径:=" + path);
File dir = new File(path);
if (!dir.exists()) {
dir.createNewFile();
}
FileOutputStream fos = new FileOutputStream(path);
fos.write(sb.toString().getBytes());
fos.close();
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
return null;
}
}
}
CrashHandler$1类代码
看完这个CrashHandler后,我们知道要在一个Application环境中让其运行,需要在自定义的application中注册,我们继承android.app.Application,添加如下的代码
那么我也看看“游戏蜂窝式如何处理的。在jadx 工具中我们选择"CrashHandler"类右键菜单中选择“FindUsage”很快就可以发现在“游戏蜂窝”中异常处理类的注册代码是:
CrashHandler.getInstance().init(getInstance());
网络搜索一下“UncaughtExceptionHandler”,应征了一句话。只要你输入关键字对,会有你要的代码。不信你看网络上代码几乎完全一样。
Android使用UncaughtExceptionHandler捕获全局异常