Android应用崩溃(Crash)日志报告

我们在开发应用的过程中不可避免的会遇到各种Crash,今天分享一下如何姿势正确的处理这些Crash来提高我们的开发效率。

对于应用的Crash处理分为测试环境和生产环境。

1、测试环境
在开发过程中为了方便快速定位崩溃所发生的代码,要求我们能在崩溃的同时将日志打印出来,最好以直观的界面显示。这里推荐大家一个开源项目:CustomActivityOnCrash
github地址:https://github.com/Ereza/CustomActivityOnCrash

2、生产环境
当应用发布上线就不能给用户显示这样的界面了所以要用一种用户比较能接受的方式处理。
在Application类中配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 初始化程序崩溃捕捉处理
*/
protected void initCrashHandler() {
if (BuildConfig.isDebug) {
CustomActivityOnCrash.install(this);
} else {
CrashHandler handler = CrashHandler.getInstance();
handler.init(getApplicationContext());
Thread.setDefaultUncaughtExceptionHandler(handler);
}
}

CrashHandler类文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
 public class CrashHandler implements UncaughtExceptionHandler {
/** Debug Log tag*/
public static final String TAG = "CrashHandler";
/** 是否开启日志输出,在Debug状态下开启,
* 在Release状态下关闭以提示程序性能
* */
public static final boolean DEBUG = false;
/** 系统默认的UncaughtException处理类 */
private UncaughtExceptionHandler mDefaultHandler;
/** CrashHandler实例 */
private static CrashHandler INSTANCE;
/** 程序的Context对象 */
private Context mContext;
/** 使用Properties来保存设备的信息和错误堆栈信息*/
private Properties mDeviceCrashInfo = new Properties();
private static final String VERSION_NAME = "versionName";
private static final String VERSION_CODE = "versionCode";
private static final String STACK_TRACE = "STACK_TRACE";
/** 错误报告文件的扩展名 */
private static final String CRASH_REPORTER_EXTENSION = ".cr";

/** 保证只有一个CrashHandler实例 */
private CrashHandler() {}

/** 获取CrashHandler实例 ,单例模式*/
public static CrashHandler getInstance() {
if (INSTANCE == null) {
INSTANCE = new CrashHandler();
}
return INSTANCE;
}

/**
* 初始化,注册Context对象,
* 获取系统默认的UncaughtException处理器,
* 设置该CrashHandler为程序的默认处理器
* @param ctx
*/
public void init(Context ctx) {
mContext = ctx;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果用户没有处理则让系统默认的异常处理器来处理
mDefaultHandler.uncaughtException(thread, ex);
} else {
//Sleep一会后结束程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Log.e(TAG, "Error : ", e);
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}

/**
* 自定义错误处理,收集错误信息
* 发送错误报告等操作均在此完成.
* 开发者可以根据自己的情况来自定义异常处理逻辑
* @param ex
* @return true:如果处理了该异常信息;否则返回false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
Log.w(TAG, "handleException --- ex==null");
return true;
}
final String msg = ex.getLocalizedMessage();
if(msg == null) {
return false;
}
//使用Toast来显示异常信息
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast toast = Toast.makeText(mContext, "程序出错,即将退出",
Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
// MsgPrompt.showMsg(mContext, "程序出错啦", msg+"\n点确认退出");
Looper.loop();
}
}.start();
//收集设备信息
collectCrashDeviceInfo(mContext);
//保存错误报告文件
saveCrashInfoToFile(ex);
//发送错误报告到服务器
//sendCrashReportsToServer(mContext);
return true;
}

/**
* 在程序启动时候, 可以调用该函数来发送以前没有发送的报告
*/
public void sendPreviousReportsToServer() {
sendCrashReportsToServer(mContext);
}
/**
* 把错误报告发送给服务器,包含新产生的和以前没发送的.
* @param ctx
*/
private void sendCrashReportsToServer(Context ctx) {
String[] crFiles = getCrashReportFiles(ctx);
if (crFiles != null && crFiles.length > 0) {
TreeSet<String> sortedFiles = new TreeSet<String>();
sortedFiles.addAll(Arrays.asList(crFiles));
for (String fileName : sortedFiles) {
File cr = new File(ctx.getFilesDir(), fileName);
postReport(cr);
cr.delete();// 删除已发送的报告
}
}
}
private void postReport(File file) {
// TODO 发送错误报告到服务器
}

/**
* 获取错误报告文件名
* @param ctx
* @return
*/
private String[] getCrashReportFiles(Context ctx) {
File filesDir = ctx.getFilesDir();
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(CRASH_REPORTER_EXTENSION);
}
};
return filesDir.list(filter);
}

/**
* 保存错误信息到文件中
* @param ex
* @return
*/
private String saveCrashInfoToFile(Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
String result = info.toString();
printWriter.close();
mDeviceCrashInfo.put("EXEPTION", ex.getLocalizedMessage());
mDeviceCrashInfo.put(STACK_TRACE, result);
try {
//long timestamp = System.currentTimeMillis();
Time t = new Time("GMT+8");
t.setToNow(); // 取得系统时间
int date = t.year * 10000 + t.month * 100 + t.monthDay;
int time = t.hour * 10000 + t.minute * 100 + t.second;
String fileName = "crash-" + date + "-" + time + CRASH_REPORTER_EXTENSION;
FileOutputStream trace = mContext.openFileOutput(fileName,
Context.MODE_PRIVATE);
mDeviceCrashInfo.store(trace, "");
trace.flush();
trace.close();
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing report file...", e);
}
return null;
}

/**
* 收集程序崩溃的设备信息
*
* @param ctx
*/
public void collectCrashDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_ACTIVITIES);
if (pi != null) {
mDeviceCrashInfo.put(VERSION_NAME,
pi.versionName == null ? "not set" : pi.versionName);
mDeviceCrashInfo.put(VERSION_CODE, ""+pi.versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Error while collect package info", e);
}
//使用反射来收集设备信息.在Build类中包含各种设备信息,
//例如: 系统版本号,设备生产商 等帮助调试程序的有用信息
//具体信息请参考后面的截图
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
mDeviceCrashInfo.put(field.getName(), ""+field.get(null));
if (DEBUG) {
Log.d(TAG, field.getName() + " : " + field.get(null));
}
} catch (Exception e) {
Log.e(TAG, "Error while collect crash info", e);
}
}
}
}

FullStackEngineer的公众号,更多分享

Share Comments