`
mmdev
  • 浏览: 12846766 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

android 处理程序全局异常和错误

 
阅读更多

本文将分析在程序出错的情况下如何收集相关的错误信息,并发送错误信息到服务器供开发者分析和调试程序。错误信息将成为您Debug的一把利刃,通过错误信息您可以最及时的掌握程序在各个系统版本和设备上的运行情况。

错误处理介绍

在一般情况下,OPhone程序出错都会出现一个提示对话框
这种情况下,用户只有点击“强行关闭”来结束程序。当该对话框出现对用户来说是相当不友好的,本文中将会告诉您如何在程序出错时不显示该对话框。
随着OPhone设备和系统版本的增加,现在在不同设备和版本上调试程序越来越麻烦,开发者不可能购买所有的设备来逐个调试程序。如果程序在模拟器上运行正常但是到最终用户手中运行却出现了错误,这种情况下如果可以收集到程序错误堆栈信息和具体设备的信息,对开发者调试程序就有莫大的帮助了。
要收集错误信息,我们需要了解两个主要接口API的使用:Android.app.Application 和java.lang.Thread.UncaughtExceptionHandler 。下面就对着两个API做简单介绍。

UncaughtExceptionHandler:线程未捕获异常控制器是用来处理未捕获异常的。如果程序出现了未捕获异常默认情况下则会出现上面所示的强行关闭对话框。在本文将实现该接口并注册为程序中的默认未捕获异常处理。这样当未捕获异常发生时,就可以做些异常处理操作,例如:收集异常信息,发送错误报告 等。

Application:在开发OPhone应用时都会和Activity打交道,而Application使用的就相对较少了。Application 在OPhone中是用来管理应用程序的全局状态的,比如载入资源文件。在应用程序启动的时候Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity或者Service。在本文将在Application中注册未捕获异常处理器。

UncaughtExceptionHandler接口实现

首先创建一个OPhone项目(项目的创建请参考OPhoneSDN上的其他文章),本文示例项目名称为:CrashReporter ;包名为:org.goodev.cr;并创建一个默认的Activity名字为:ReporterTest。然后创建CrashHandler类实现UncaughtExceptionHandler接口,并实现其函数:public void uncaughtException(Thread thread, Throwable ex)。CrashHandler类实现了错误报告的主要处理逻辑,该类代码如下(在代码中会有详细注释来解释各种处理情况):<!--IWMS_AD_BEGIN-->

<!--IWMS_AD_END-->

  1. packageorg.goodev.cr;
  2. Import省略...;
  3. /**
  4. *UncaughtException处理类,当程序发生Uncaught异常的时候,有该类
  5. *来接管程序,并记录发送错误报告.
  6. *
  7. */
  8. publicclassCrashHandlerimplementsUncaughtExceptionHandler{
  9. /**DebugLogtag*/
  10. publicstaticfinalStringTAG="CrashHandler";
  11. /**是否开启日志输出,在Debug状态下开启,
  12. *在Release状态下关闭以提示程序性能
  13. **/
  14. publicstaticfinalbooleanDEBUG=true;
  15. /**系统默认的UncaughtException处理类*/
  16. privateThread.UncaughtExceptionHandlermDefaultHandler;
  17. /**CrashHandler实例*/
  18. privatestaticCrashHandlerINSTANCE;
  19. /**程序的Context对象*/
  20. privateContextmContext;
  21. /**使用Properties来保存设备的信息和错误堆栈信息*/
  22. privatePropertiesmDeviceCrashInfo=newProperties();
  23. privatestaticfinalStringVERSION_NAME="versionName";
  24. privatestaticfinalStringVERSION_CODE="versionCode";
  25. privatestaticfinalStringSTACK_TRACE="STACK_TRACE";
  26. /**错误报告文件的扩展名*/
  27. privatestaticfinalStringCRASH_REPORTER_EXTENSION=".cr";
  28. /**保证只有一个CrashHandler实例*/
  29. privateCrashHandler(){}
  30. /**获取CrashHandler实例,单例模式*/
  31. publicstaticCrashHandlergetInstance(){
  32. if(INSTANCE==null){
  33. INSTANCE=newCrashHandler();
  34. }
  35. returnINSTANCE;
  36. }
  37. /**
  38. *初始化,注册Context对象,
  39. *获取系统默认的UncaughtException处理器,
  40. *设置该CrashHandler为程序的默认处理器
  41. *
  42. *@paramctx
  43. */
  44. publicvoidinit(Contextctx){
  45. mContext=ctx;
  46. mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();
  47. Thread.setDefaultUncaughtExceptionHandler(this);
  48. }
  49. /**
  50. *当UncaughtException发生时会转入该函数来处理
  51. */
  52. @Override
  53. publicvoiduncaughtException(Threadthread,Throwableex){
  54. if(!handleException(ex)&&mDefaultHandler!=null){
  55. //如果用户没有处理则让系统默认的异常处理器来处理
  56. mDefaultHandler.uncaughtException(thread,ex);
  57. }else{
  58. //Sleep一会后结束程序
  59. try{
  60. Thread.sleep(3000);
  61. }catch(InterruptedExceptione){
  62. Log.e(TAG,"Error:",e);
  63. }
  64. Android.os.Process.killProcess(android.os.Process.myPid());
  65. System.exit(10);
  66. }
  67. }
  68. /**
  69. *自定义错误处理,收集错误信息
  70. *发送错误报告等操作均在此完成.
  71. *开发者可以根据自己的情况来自定义异常处理逻辑
  72. *@paramex
  73. *@returntrue:如果处理了该异常信息;否则返回false
  74. */
  75. privatebooleanhandleException(Throwableex){
  76. if(ex==null){
  77. returntrue;
  78. }
  79. finalStringmsg=ex.getLocalizedMessage();
  80. //使用Toast来显示异常信息
  81. newThread(){
  82. @Override
  83. publicvoidrun(){
  84. Looper.prepare();
  85. Toast.makeText(mContext,"程序出错啦:"+msg,Toast.LENGTH_LONG)
  86. .show();
  87. Looper.loop();
  88. }
  89. }.start();
  90. //收集设备信息
  91. collectCrashDeviceInfo(mContext);
  92. //保存错误报告文件
  93. StringcrashFileName=saveCrashInfoToFile(ex);
  94. //发送错误报告到服务器
  95. sendCrashReportsToServer(mContext);
  96. returntrue;
  97. }
  98. /**
  99. *在程序启动时候,可以调用该函数来发送以前没有发送的报告
  100. */
  101. publicvoidsendPreviousReportsToServer(){
  102. sendCrashReportsToServer(mContext);
  103. }
  104. /**
  105. *把错误报告发送给服务器,包含新产生的和以前没发送的.
  106. *
  107. *@paramctx
  108. */
  109. privatevoidsendCrashReportsToServer(Contextctx){
  110. String[]crFiles=getCrashReportFiles(ctx);
  111. if(crFiles!=null&&crFiles.length>0){
  112. TreeSet<String>sortedFiles=newTreeSet<String>();
  113. sortedFiles.addAll(Arrays.asList(crFiles));
  114. for(StringfileName:sortedFiles){
  115. Filecr=newFile(ctx.getFilesDir(),fileName);
  116. postReport(cr);
  117. cr.delete();//删除已发送的报告
  118. }
  119. }
  120. }
  121. privatevoidpostReport(Filefile){
  122. //TODO使用HTTPPost发送错误报告到服务器
  123. //这里不再详述,开发者可以根据OPhoneSDN上的其他网络操作
  124. //教程来提交错误报告
  125. }
  126. /**
  127. *获取错误报告文件名
  128. *@paramctx
  129. *@return
  130. */
  131. privateString[]getCrashReportFiles(Contextctx){
  132. FilefilesDir=ctx.getFilesDir();
  133. FilenameFilterfilter=newFilenameFilter(){
  134. publicbooleanaccept(Filedir,Stringname){
  135. returnname.endsWith(CRASH_REPORTER_EXTENSION);
  136. }
  137. };
  138. returnfilesDir.list(filter);
  139. }
  140. /**
  141. *保存错误信息到文件中
  142. *@paramex
  143. *@return
  144. */
  145. privateStringsaveCrashInfoToFile(Throwableex){
  146. Writerinfo=newStringWriter();
  147. PrintWriterprintWriter=newPrintWriter(info);
  148. ex.printStackTrace(printWriter);
  149. Throwablecause=ex.getCause();
  150. while(cause!=null){
  151. cause.printStackTrace(printWriter);
  152. cause=cause.getCause();
  153. }
  154. Stringresult=info.toString();
  155. printWriter.close();
  156. mDeviceCrashInfo.put(STACK_TRACE,result);
  157. try{
  158. longtimestamp=System.currentTimeMillis();
  159. StringfileName="crash-"+timestamp+CRASH_REPORTER_EXTENSION;
  160. FileOutputStreamtrace=mContext.openFileOutput(fileName,
  161. Context.MODE_PRIVATE);
  162. mDeviceCrashInfo.store(trace,"");
  163. trace.flush();
  164. trace.close();
  165. returnfileName;
  166. }catch(Exceptione){
  167. Log.e(TAG,"anerroroccuredwhilewritingreportfile...",e);
  168. }
  169. returnnull;
  170. }
  171. /**
  172. *收集程序崩溃的设备信息
  173. *
  174. *@paramctx
  175. */
  176. publicvoidcollectCrashDeviceInfo(Contextctx){
  177. try{
  178. PackageManagerpm=ctx.getPackageManager();
  179. PackageInfopi=pm.getPackageInfo(ctx.getPackageName(),
  180. PackageManager.GET_ACTIVITIES);
  181. if(pi!=null){
  182. mDeviceCrashInfo.put(VERSION_NAME,
  183. pi.versionName==null?"notset":pi.versionName);
  184. mDeviceCrashInfo.put(VERSION_CODE,pi.versionCode);
  185. }
  186. }catch(NameNotFoundExceptione){
  187. Log.e(TAG,"Errorwhilecollectpackageinfo",e);
  188. }
  189. //使用反射来收集设备信息.在Build类中包含各种设备信息,
  190. //例如:系统版本号,设备生产商等帮助调试程序的有用信息
  191. //具体信息请参考后面的截图
  192. Field[]fields=Build.class.getDeclaredFields();
  193. for(Fieldfield:fields){
  194. try{
  195. field.setAccessible(true);
  196. mDeviceCrashInfo.put(field.getName(),field.get(null));
  197. if(DEBUG){
  198. Log.d(TAG,field.getName()+":"+field.get(null));
  199. }
  200. }catch(Exceptione){
  201. Log.e(TAG,"Errorwhilecollectcrashinfo",e);
  202. }
  203. }
  204. }
  205. }

在上面CrashHandler实现中,当错误发生的时候使用Toast显示错误信息,然后收集错误报告并保存在文件中。 发送错误报告代码请读者自己实现。在uncaughtException函数中调用了Thread.sleep(3000);来让线程停止一会是为了显示Toast信息给用户,然后Kill程序。如果你不用Toast来显示信息则可以去除该代码。除了Toast外,开发者还可以选择使用Notification来显示错误内容并让用户选择是否提交错误报告而不是自动提交。关于Notification的实现请读者参考:NotificationManager。在发送错误报道的时候,可以先检测网络是否可用,如果不可用则可以在以后网络情况可用的情况下发送。 网络监测代码如下:

  1. /**
  2. *检测网络连接是否可用
  3. *@paramctx
  4. *@returntrue可用;false不可用
  5. */
  6. privatebooleanisNetworkAvailable(Contextctx){
  7. ConnectivityManagercm=
  8. (ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
  9. if(cm==null){
  10. returnfalse;
  11. }
  12. NetworkInfo[]netinfo=cm.getAllNetworkInfo();
  13. if(netinfo==null){
  14. returnfalse;
  15. }
  16. for(inti=0;i<netinfo.length;i++){
  17. if(netinfo[i].isConnected()){
  18. returntrue;
  19. }
  20. }
  21. returnfalse;
  22. }

Application 实现

实现一个自定义Application来注册CrashHandler. 代码如下:

  1. publicclassCrashApplicationextendsApplication{
  2. @Override
  3. publicvoidonCreate(){
  4. super.onCreate();
  5. CrashHandlercrashHandler=CrashHandler.getInstance();
  6. //注册crashHandler
  7. crashHandler.init(getApplicationContext());
  8. //发送以前没发送的报告(可选)
  9. crashHandler.sendPreviousReportsToServer();
  10. }
  11. }

AndroidManifest.xml中注册

最后只要在AndroidManifest.xml中注册CrashApplication就可以了。代码如下:

  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <manifestxmlns:Android="http://schemas.android.com/apk/res/android"
  3. package="org.goodev.cr"
  4. Android:versionCode="1"
  5. Android:versionName="1.0">
  6. <applicationAndroid:icon="@drawable/icon"android:label="@string/app_name"
  7. Android:name=".CrashApplication">
  8. <activityAndroid:name=".ReporterTest"
  9. Android:label="@string/app_name">
  10. <intent-filter>
  11. <actionAndroid:name="android.intent.action.MAIN"/>
  12. <categoryAndroid:name="android.intent.category.LAUNCHER"/>
  13. </intent-filter>
  14. </activity>
  15. </application>
  16. </manifest>

总结:通过本文示例的方式,开发者可以在程序中收集详细的崩溃信息,从而为调试程序带来便利,如果您的程序还没有该功能赶快加入吧。crashReporter.zip中包含本文使用的项目文件及资源。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics