(一):写在前面的话
接着上一篇继续更新,上一篇文章已经把FastDev4Android项目数据轻量级缓存ACache组件做了讲解和使用。今天项目更新客户端崩溃异常捕捉组件CustomCrash的讲解和使用。
在平时我们都知道,Android系统的手机和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,设备比较多,所以在程序发布出去之后,如果出现了崩溃现象,开发者应该及时获取在该设备上导致崩溃的信息,同时可以获取设备的相关信息和用户信息,这对于下一个版本的 BUG 修复帮助极大,所以今天就来介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。同时替换系统默认的崩溃弹框,提升应用的用户体验。
首先我们来看一下系统默认的崩溃弹框显示:
看上面的运行状态,一旦我们的应用出现了异常崩溃,立马会弹框,点击OK应用就退出了,这样用户也不知道发生了什么情况,一下子应用的用户体验下降了很多。更加严重的是,作为我们开发者还不知道云在用户手机的APP什么时候崩溃的,到底因为什么原因崩溃的。 OK下面我们来具体实现自定义的拦截崩溃异常的功能;
(二):具体实现
2.1:定义类
首先我们需要自定义一个实现Thead.UncaughtExceptionHandler的类,然后实现内部接口中定义的方法: void uncaughtException(Thread thread, Throwable ex);
具体如下:
public class CustomCrash implements Thread.UncaughtExceptionHandler
/* * (non-Javadoc) 进行重写捕捉异常 * * @see * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang * .Thread, java.lang.Throwable) */
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if(type_save==TYPE_SAVE_SDCARD){
// 1,保存信息到sdcard中
saveToSdcard(mContext, ex);
}else if(type_save==TYPE_SAVE_REMOTE){
// 2,异常崩溃信息投递到服务器
saveToServer(mContext,ex);
}
// 3,应用准备退出
showToast(mContext, "很抱歉,程序发生异常,即将推出.");
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ManagerActivity.getInstance().finishActivity();
android.os.Process.killProcess(android.os.Process.myPid());
}
2.2.上下文注册
我们需要把当前应用的上下文注册到系统的异常处理器中。这样就让系统执行我们自定义的异常捕捉器。
public void setCustomCrashInfo(Context pContext) {
this.mContext = pContext;
Thread.setDefaultUncaughtExceptionHandler(this);
}
2.3:崩溃异常日志保存
①:数据保存到SDCard中,直接转换异常日志信息,写入SDCard文件中如下:
/** * 保存异常信息到sdcard中 * * @param pContext * @param ex * 异常信息对象 */
private void saveToSdcard(Context pContext, Throwable ex) {
String fileName = null;
StringBuffer sBuffer = new StringBuffer();
// 添加异常信息
sBuffer.append(getExceptionInfo(ex));
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
File file1 = new File(CRASH_SAVE_SDPATH);
if (!file1.exists()) {
file1.mkdir();
}
fileName = file1.toString() + File.separator + paserTime(System.currentTimeMillis()) + ".log";
File file2 = new File(fileName);
FileOutputStream fos;
try {
fos = new FileOutputStream(file2);
fos.write(sBuffer.toString().getBytes());
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
②:数据POST投递到服务器中,进行接口存入相应的文件或者数据库中
/** * 进行把数据投递至服务器 * @param pContext * @param ex 崩溃异常 */
private void saveToServer(Context pContext,Throwable ex){
final String carsh_log=getExceptionInfo(ex);
new Thread(new Runnable() {
@Override
public void run() {
HashMap<String,String> params=new HashMap<String,String>();
params.put("crash_log",carsh_log);
String result= IoUtils.responseFromServiceByGetNo(CARSH_LOG_DELIVER, params);
if (result.equals("1")){
Log.d(TAG,"崩溃日志投递成功...");
}else {
Log.d(TAG,"崩溃日志投递失败...");
}
}
}).start();
}
2.4:异常捕捉器具体使用:
我们需要在自定义的Application初始化方法进行初始化我们的捕捉器,然后设置改变系统捕捉器处理方式:
@Override
public void onCreate() {
super.onCreate();
this.instance=this;
//初始化崩溃日志收集器
CustomCrash mCustomCrash=CustomCrash.getInstance();
mCustomCrash.setCustomCrashInfo(this);
}
OK下面我们来具体来使用一下,我们故意制造一个空指针异常:结果分别如下:
2.5:由于上面对于类的核心方法进行了讲解,并且该类其他也没有多少行代码,方便大家阅读这边直接把整个类复制如下,并且为了大家测试使用,类中崩溃日志投递的地址也是可以正常使用的。
package com.chinaztt.fda.crash;
import android.content.Context;
import android.os.Environment;
import android.os.Looper;
import android.widget.Toast;
import com.chinaztt.fda.utils.IoUtils;
import com.chinaztt.fda.utils.Log;
import com.chinaztt.fda.utils.ManagerActivity;
import com.chinaztt.fda.utils.StrUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;
/** * 当前类注释:客户端运行 异常崩溃数据扑捉异常保存SD卡或者实时投递服务器工具类 * 项目名:FastDev4Android * 包名:com.chinaztt.fda.crash * 作者:江清清 on 15/10/26 13:29 * 邮箱:[email protected] * QQ: 781931404 * 公司:江苏中天科技软件技术有限公司 */
public class CustomCrash implements Thread.UncaughtExceptionHandler{
private static final String TAG="CustomCrash";
private static final int TYPE_SAVE_SDCARD=1; //崩溃日志保存本地SDCard --建议开发模式使用
private static final int TYPE_SAVE_REMOTE=2; //崩溃日志保存远端服务器 --建议生产模式使用
private int type_save=2; //崩溃保存日志模式 默认为2,采用保存Web服务器
private static final String CRASH_SAVE_SDPATH="sdcard/fda_cache/"; //崩溃日志SD卡保存路径
private static final String CARSH_LOG_DELIVER="http://img2.xxh.cc:8080/SalesWebTest/CrashDeliver";
private static CustomCrash instance = new CustomCrash();
private Context mContext;
private CustomCrash() {
}
/** * * @return */
public static CustomCrash getInstance() {
return instance;
}
/* * (non-Javadoc) 进行重写捕捉异常 * * @see * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang * .Thread, java.lang.Throwable) */
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if(type_save==TYPE_SAVE_SDCARD){
// 1,保存信息到sdcard中
saveToSdcard(mContext, ex);
}else if(type_save==TYPE_SAVE_REMOTE){
// 2,异常崩溃信息投递到服务器
saveToServer(mContext,ex);
}
// 3,应用准备退出
showToast(mContext, "很抱歉,程序发生异常,即将推出.");
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ManagerActivity.getInstance().finishActivity();
android.os.Process.killProcess(android.os.Process.myPid());
}
/** * 设置自定异常处理类 * * @param pContext */
public void setCustomCrashInfo(Context pContext) {
this.mContext = pContext;
Thread.setDefaultUncaughtExceptionHandler(this);
}
/** * 保存异常信息到sdcard中 * * @param pContext * @param ex * 异常信息对象 */
private void saveToSdcard(Context pContext, Throwable ex) {
String fileName = null;
StringBuffer sBuffer = new StringBuffer();
// 添加异常信息
sBuffer.append(getExceptionInfo(ex));
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
File file1 = new File(CRASH_SAVE_SDPATH);
if (!file1.exists()) {
file1.mkdir();
}
fileName = file1.toString() + File.separator + paserTime(System.currentTimeMillis()) + ".log";
File file2 = new File(fileName);
FileOutputStream fos;
try {
fos = new FileOutputStream(file2);
fos.write(sBuffer.toString().getBytes());
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** * 进行把数据投递至服务器 * @param pContext * @param ex 崩溃异常 */
private void saveToServer(Context pContext,Throwable ex){
final String carsh_log=getExceptionInfo(ex);
new Thread(new Runnable() {
@Override
public void run() {
HashMap<String,String> params=new HashMap<String,String>();
params.put("crash_log",carsh_log);
String result= IoUtils.responseFromServiceByGetNo(CARSH_LOG_DELIVER, params);
if (result.equals("1")){
Log.d(TAG,"崩溃日志投递成功...");
}else {
Log.d(TAG,"崩溃日志投递失败...");
}
}
}).start();
}
/** * 获取并且转化异常信息 * 同时可以进行投递相关的设备,用户信息 * @param ex * @return 异常信息的字符串形式 */
private String getExceptionInfo(Throwable ex) {
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw));
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("---------Crash Log Begin---------\n");
//在这边可以进行相关设备信息投递--这边就稍微设置几个吧
//其他设备和用户信息大家可以自己去扩展收集上传投递
stringBuffer.append("SystemVersion:"+ StrUtils.getLocalSystemVersion()+"\n");
stringBuffer.append(sw.toString()+"\n");
stringBuffer.append("---------Crash Log End---------\n");
return stringBuffer.toString();
}
/** * 进行弹出框提示 * * @param pContext * @param msg */
private void showToast(final Context pContext, final String msg) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(pContext, msg, Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
}
/** * 将毫秒数转换成yyyy-MM-dd-HH-mm-ss的格式 * @param milliseconds * @return */
private String paserTime(long milliseconds) {
System.setProperty("user.timezone", "Asia/Shanghai");
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone.setDefault(tz);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String times = format.format(new Date(milliseconds));
return times;
}
}
到此为止我们今天自定义异常捕捉CustomCrash的讲解和使用结果,详细代码项目地址:
https://github.com/jiangqqlmj/FastDev4Android
同时欢迎大家star和fork整个开源快速开发框架项目~如果有什么意见和反馈,欢迎留言,必定第一时间回复。也欢迎有同样兴趣的童鞋加入到该项目中来,一起维护该项目。