为什么要捕获Crash信息:
开发阶段,我们会使用Monitor查看logcat 信息,但由于很多原因,Android Monitor抓不到logcat信息。 因此需要一个CrashHandler来将Crash写入到本地或者上传到服务器方便我们分析。
线上应用:
崩溃率是衡量应用稳定性的重要标准,那么应用上线以后 我们无法向用户借手机来分析崩溃原因。为了减低崩溃率,这个时候需要CrashHandler 来帮我们将崩溃信息返回给后台,以便及时修复。
如何捕获捕获Crash分如下三步:
1、实现Thread.UncaughtExceptionHandler接口,并重写uncaughtException方法,在uncaughtException 方法中接收异常信息
2、调用Thread.setDefaultUncaughtExceptionHandler(CrashHandler) ,来使用我们自定义的CrashHandler来取代系统默认的CrashHandler
3、异常信息的获取、本地写入以及异常信息的上传
首先贴自定义的RCrashHandler
/**
* Created by Owen Chan
* On 2017-06-07.
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler INSTANCE;
private static Context mContext;
private static PendingIntent restartIntent;
private Thread.UncaughtExceptionHandler mDefaultHandler;
private CrashUploader crashUploader;
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
public static final String EXCEPTION_INFO = "EXCEPTION_INFO";
public static final String PACKAGE_INFO = "PACKAGE_INFO";
public static final String DEVICE_INFO = "DEVICE_INFO";
public static final String SYSTEM_INFO = "SYSTEM_INFO";
public static final String SECURE_INFO = "SECURE_INFO";
public static final String MEM_INFO = "MEM_INFO";
private String mExceptionInfo;
private String mMemInfo;
private ConcurrentHashMap mPackageInfo = new ConcurrentHashMap<>();
private ConcurrentHashMap mDeviceInfo = new ConcurrentHashMap<>();
private ConcurrentHashMap mSystemInfo = new ConcurrentHashMap<>();
private ConcurrentHashMap mSecureInfo = new ConcurrentHashMap<>();
private ConcurrentHashMap totalInfo = new ConcurrentHashMap<>();
private CrashHandler() {
}
public static CrashHandler getInstance() {
if (INSTANCE == null) {
synchronized (CrashHandler.class) {
if (INSTANCE == null) {
INSTANCE = new CrashHandler();
}
}
}
return INSTANCE;
}
public void init(Context context, CrashUploader crashUploader, PendingIntent pendingIntent) {
mContext = context;
this.crashUploader = crashUploader;
this.restartIntent = pendingIntent;
//保存一份系统默认的CrashHandler
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//使用我们自定义的异常处理器替换程序默认的
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* @param t 出现未捕获异常的线程
* @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!catchCrashException(e) && mDefaultHandler != null) {
//没有自定义的CrashHandler的时候就调用系统默认的异常处理方式
mDefaultHandler.uncaughtException(t, e);
} else {
//退出应用
killProcess();
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @param ex 未捕获的异常
* @return true:如果处理了该异常信息;否则返回false.
*/
private boolean catchCrashException(Throwable ex) {
if (ex == null) {
return false;
}
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(ClientApp.getInstance().getApplicationContext(), "Duang~~崩啦~~崩啦~~~~", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
collectInfo(ex);
//保存日志文件
saveCrashInfo2File();
//上传崩溃信息
uploadCrashMessage(totalInfo);
return true;
}
/**
* 退出应用
*/
private static void killProcess() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e("application", "error : ", e);
}
// 退出程序
AlarmManager mgr = (AlarmManager) ClientApp.getInstance().getApplicationContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 2000, restartIntent); // 2秒钟后重启应用
Process.killProcess(Process.myPid());
System.exit(1);
}
/**
* 获取异常信息和设备参数信息
*/
private void collectInfo(Throwable ex) {
mExceptionInfo = collectExceptionInfo(ex);
collectPackageInfo();
collectBuildInfos();
collectSystemInfos();
collectSecureInfo();
mMemInfo = collectMemInfo();
totalInfo.put(EXCEPTION_INFO, mExceptionInfo);
totalInfo.put(PACKAGE_INFO, mPackageInfo);
totalInfo.put(DEVICE_INFO, mDeviceInfo);
totalInfo.put(SYSTEM_INFO, mSecureInfo);
totalInfo.put(SECURE_INFO, mSecureInfo);
totalInfo.put(MEM_INFO, MEM_INFO);
}
/**
* 获取捕获异常的信息
*/
private String collectExceptionInfo(Throwable ex) {
Writer mWriter = new StringWriter();
PrintWriter mPrintWriter = new PrintWriter(mWriter);
ex.printStackTrace(mPrintWriter);
ex.printStackTrace();
Throwable mThrowable = ex.getCause();
// 迭代栈队列把所有的异常信息写入writer中
while (mThrowable != null) {
mThrowable.printStackTrace(mPrintWriter);
// 换行 每个个异常栈之间换行
mPrintWriter.append("\r\n");
mThrowable = mThrowable.getCause();
}
// 记得关闭
mPrintWriter.close();
return mWriter.toString();
}
/**
* 获取应用包参数信息
*/
private void collectPackageInfo() {
try {
// 获得包管理器
PackageManager mPackageManager = mContext.getPackageManager();
// 得到该应用的信息,即主Activity
PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
if (mPackageInfo != null) {
String versionName = mPackageInfo.versionName == null ? "null" : mPackageInfo.versionName;
String versionCode = mPackageInfo.versionCode + "";
this.mPackageInfo.put("VersionName", versionName);
this.mPackageInfo.put("VersionCode", versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/**
* 从系统属性中提取设备硬件和版本信息
*/
private void collectBuildInfos() {
// 反射机制
Field[] mFields = Build.class.getDeclaredFields();
// 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
for (Field field : mFields) {
try {
field.setAccessible(true);
mDeviceInfo.put(field.getName(), field.get("").toString());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 获取系统常规设定属性
*/
private void collectSystemInfos() {
Field[] fields = Settings.System.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class)
&& field.getType() == String.class) {
try {
String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSystemInfo.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取系统安全设置信息
*/
private void collectSecureInfo() {
Field[] fields = Settings.Secure.class.getFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Deprecated.class)
&& field.getType() == String.class
&& field.getName().startsWith("WIFI_AP")) {
try {
String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
if (value != null) {
mSecureInfo.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取内存信息
*/
private String collectMemInfo() {
BufferedReader br = null;
StringBuffer sb = new StringBuffer();
ArrayList commandLine = new ArrayList<>();
commandLine.add("cat");
commandLine.add("/proc/meminfo");
commandLine.add(Integer.toString(Process.myPid()));
try {
java.lang.Process process = Runtime.getRuntime()
.exec(commandLine.toArray(new String[commandLine.size()]));
br = new BufferedReader(new InputStreamReader(process.getInputStream()), 1024);
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
sb.append(line);
sb.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
/**
* 将崩溃日志信息写入本地文件
*/
private String saveCrashInfo2File() {
StringBuffer mStringBuffer = getInfoStr(mPackageInfo);
mStringBuffer.append(mExceptionInfo);
// 保存文件,设置文件名
String mTime = formatter.format(new Date());
String mFileName = "crash-" + mTime + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
try {
File mDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/hive-crash");
if (!mDirectory.exists()) {
boolean success = mDirectory.mkdirs();
}
FileOutputStream mFileOutputStream = new FileOutputStream(mDirectory + File.separator + mFileName);
mFileOutputStream.write(mStringBuffer.toString().getBytes());
mFileOutputStream.close();
return mFileName;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 将HashMap遍历转换成StringBuffer
*/
@NonNull
private static StringBuffer getInfoStr(ConcurrentHashMap info) {
StringBuffer mStringBuffer = new StringBuffer();
for (Map.Entry entry : info.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
mStringBuffer.append(key + "=" + value + "\r\n");
}
return mStringBuffer;
}
/**
* 上传崩溃信息到服务器
*/
private void uploadCrashMessage(ConcurrentHashMap info) {
crashUploader.uploadCrashMessage(info);
}
/**
* 崩溃信息上传接口回调
*/
public interface CrashUploader {
void uploadCrashMessage(ConcurrentHashMap info);
}
}
Application 中的初始化:
/**
* Created by Owen Chan
* On 2017-06-07.
*/
public class ClientApp extends Application {
private static ClientApp INSTANCE;
private PendingIntent restartIntent;
@Override
public void onCreate() {
super.onCreate();
CrashHandler.CrashUploader crashUploader = new CrashHandler.CrashUploader() {
@Override
public void uploadCrashMessage(ConcurrentHashMap info) {
//这个地方自定义崩溃信息的上传
}
};
INSTANCE = this;
// 以下用来捕获程序崩溃异常
Intent intent = new Intent();
intent.setClassName("com.owen.crashhandler", "com.owen.crashhandler.main.MainActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
restartIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
CrashHandler.getInstance().init(this, crashUploader, restartIntent);
}
public synchronized static ClientApp getInstance() {
return INSTANCE;
}
}