先建立一个crash项目,项目结构如图:
在MainActivity.java代码中,代码是这样写的:
[java] view plaincopyprint?
1. package
2.
3. import
4. import
5.
6. public class MainActivity extends
7.
8. private
9.
10. @Override
11. public void
12. super.onCreate(savedInstanceState);
13. "any string"));
14. }
15. }
我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:
遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。
我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的bug的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。
接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。
Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。
Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
[java] view plaincopyprint?
1. package
2.
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12. import
13. import
14. import
15.
16. import
17. import
18. import
19. import
20. import
21. import
22. import
23. import
24. import
25.
26. /**
27. * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
28. *
29. * @author user
30. *
31. */
32. public class CrashHandler implements
33.
34. public static final String TAG = "CrashHandler";
35.
36. //系统默认的UncaughtException处理类
37. private
38. //CrashHandler实例
39. private static CrashHandler INSTANCE = new
40. //程序的Context对象
41. private
42. //用来存储设备信息和异常信息
43. private Map<String, String> infos = new
44.
45. //用于格式化日期,作为日志文件名的一部分
46. private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
47.
48. /** 保证只有一个CrashHandler实例 */
49. private
50. }
51.
52. /** 获取CrashHandler实例 ,单例模式 */
53. public static
54. return
55. }
56.
57. /**
58. * 初始化
59. *
60. * @param context
61. */
62. public void
63. mContext = context;
64. //获取系统默认的UncaughtException处理器
65. mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
66. //设置该CrashHandler为程序的默认处理器
67. this);
68. }
69.
70. /**
71. * 当UncaughtException发生时会转入该函数来处理
72. */
73. @Override
74. public void
75. if (!handleException(ex) && mDefaultHandler != null) {
76. //如果用户没有处理则让系统默认的异常处理器来处理
77. mDefaultHandler.uncaughtException(thread, ex);
78. else
79. try
80. 3000);
81. catch
82. "error : ", e);
83. }
84. //退出程序
85. android.os.Process.killProcess(android.os.Process.myPid());
86. 1);
87. }
88. }
89.
90. /**
91. * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
92. *
93. * @param ex
94. * @return true:如果处理了该异常信息;否则返回false.
95. */
96. private boolean
97. if (ex == null) {
98. return false;
99. }
100. //使用Toast来显示异常信息
101. new
102. @Override
103. public void
104. Looper.prepare();
105. "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
106. Looper.loop();
107. }
108. }.start();
109. //收集设备参数信息
110. collectDeviceInfo(mContext);
111. //保存日志文件
112. saveCrashInfo2File(ex);
113. return true;
114. }
115.
116. /**
117. * 收集设备参数信息
118. * @param ctx
119. */
120. public void
121. try
122. PackageManager pm = ctx.getPackageManager();
123. PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
124. if (pi != null) {
125. null ? "null"
126. "";
127. "versionName", versionName);
128. "versionCode", versionCode);
129. }
130. catch
131. "an error occured when collect package info", e);
132. }
133. class.getDeclaredFields();
134. for
135. try
136. true);
137. null).toString());
138. " : " + field.get(null));
139. catch
140. "an error occured when collect crash info", e);
141. }
142. }
143. }
144.
145. /**
146. * 保存错误信息到文件中
147. *
148. * @param ex
149. * @return 返回文件名称,便于将文件传送到服务器
150. */
151. private
152.
153. new
154. for
155. String key = entry.getKey();
156. String value = entry.getValue();
157. "=" + value + "\n");
158. }
159.
160. new
161. new
162. ex.printStackTrace(printWriter);
163. Throwable cause = ex.getCause();
164. while (cause != null) {
165. cause.printStackTrace(printWriter);
166. cause = cause.getCause();
167. }
168. printWriter.close();
169. String result = writer.toString();
170. sb.append(result);
171. try
172. long
173. new
174. "crash-" + time + "-" + timestamp + ".log";
175. if
176. "/sdcard/crash/";
177. new
178. if
179. dir.mkdirs();
180. }
181. new
182. fos.write(sb.toString().getBytes());
183. fos.close();
184. }
185. return
186. catch
187. "an error occured while writing file...", e);
188. }
189. return null;
190. }
191. }
在收集异常信息时,朋友们也可以使用Properties,因为Properties有一个很便捷的方法properties.store(OutputStream out, String comments),用来将Properties实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成Map来存放这些信息,然后生成文件时稍加了些操作。
完成这个CrashHandler后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,CrashApplication.java代码如下:
[java] view plaincopyprint?
1. package
2.
3. import
4.
5. public class CrashApplication extends
6. @Override
7. public void
8. super.onCreate();
9. CrashHandler crashHandler = CrashHandler.getInstance();
10. crashHandler.init(getApplicationContext());
11. }
12. }
最后,为了让我们的CrashApplication取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:
[html] view plaincopyprint?
1. <application android:name=".CrashApplication" ...>
2. </application>
因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:
[html] view plaincopyprint?
1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
搞定了上边的步骤之后,我们来运行一下这个项目:
看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。
然后看一下SDCARD生成的文件:
用文本编辑器打开日志文件,看一段日志信息:
[java] view plaincopyprint?
1. CPU_ABI=armeabi
2. CPU_ABI2=unknown
3. ID=FRF91
4. MANUFACTURER=unknown
5. BRAND=generic
6. TYPE=eng
7. ......
8. Caused by: java.lang.NullPointerException
9. 13)
10. 1047)
11. 2627)
12. 11
这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器。
不过在使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
[java] view plaincopyprint?
1. /**
2. * 网络是否可用
3. *
4. * @param context
5. * @return
6. */
7. public static boolean
8. ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
9. NetworkInfo[] info = mgr.getAllNetworkInfo();
10. if (info != null) {
11. for (int i = 0; i < info.length; i++) {
12. if
13. return true;
14. }
15. }
16. }
17. return false;
18. }