在开发过程中,虽然经过测试,但在发布后,在广大用户各种各样的运行环境和操作下,可能会发生一些异想不到的错误导致程序崩溃。将这些错误信息收集起来并反馈给开发者,对于开发者改进优化程序是相当重要的。好了,下面就来实现这种功能吧。

经实践,发现Android,在代码中未捕获的异常如果是UI线程抛出的,则异常发生后无法再唤醒新的界面。所以对于所抛异常的处理Sodino的做法是:
1.对于UI线程(即Android中的主线程)抛出的未捕获异常,将这些异常信息存储起来然后关闭到整个应用程序。当用户下一次开启程序时,则进入崩溃信息反馈界面让用户将出错信息以Email的形式发送给开发者。
2.对于非UI线程抛出的异常,则立即唤醒崩溃信息反馈界面提示用户将出错信息发送Email。

效果图如下[CSDN]:


过程了解了,则需要了解的几个知识点如下:
1.拦截UncaughtException
Application.onCreate()是整个Android应用的入口方法。在该方法中执行如下代码即可拦截UncaughtException:

  1. ueHandler=newUEHandler(this);
  2. //设置异常处理实例
  3. Thread.setDefaultUncaughtExceptionHandler(ueHandler);

2.抓取导致程序崩溃的异常信息
UEHandler是Thread.UncaughtExceptionHandler的实现类,在其public void uncaughtException(Thread thread, Throwable ex)的实现中可以获取崩溃信息,代码如下:

  1. //fetchExcpetionInfo
  2. Stringinfo=null;
  3. ByteArrayOutputStreambaos=null;
  4. PrintStreamprintStream=null;
  5. try{
  6. baos=newByteArrayOutputStream();
  7. printStream=newPrintStream(baos);
  8. ex.printStackTrace(printStream);
  9. byte[]data=baos.toByteArray();
  10. info=newString(data);
  11. data=null;
  12. }catch(Exceptione){
  13. e.printStackTrace();
  14. }finally{
  15. try{
  16. if(printStream!=null){
  17. printStream.close();
  18. }
  19. if(baos!=null){
  20. baos.close();
  21. }
  22. }catch(Exceptione){
  23. e.printStackTrace();
  24. }
  25. }


3.程序抛异常后,要关闭整个应用
悲催的程序员,唉,以下三种方式都无效了,咋办啊!!!
3.1android.os.Process.killProcess(android.os.Process.myPid());
3.2ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
am.restartPackage("lab.sodino.errorreport");
3.3System.exit(0)

好吧,毛主席告诉我们:自己动手丰衣足食。
SoftApplication中声明一个变量need2Exit,其值为true标识当前的程序需要完整退出;为false时该干嘛干嘛去。该变量在应用的启动Activity.onCreate()处赋值为false。
在捕获了崩溃信息后,调用SoftApplication.setNeed2Exit(true)标识程序需要退出,并finish()掉ActErrorReport,这时ActErrorReport退栈,抛错的ActOccurError占据手机屏幕,根据Activity的生命周期其要调用onStart(),则我们在onStart()处读取need2Exit的状态,若为true,则也关闭到当前的Activity,则退出了整个应用了。此方法可以解决一次性退出已开启了多个Activity的Application。详细代码请阅读下面的示例源码。

好了,代码如下:

lab.sodino.errorreport.SoftApplication.java

view plain
  1. packagelab.sodino.errorreport;
  2. importjava.io.File;
  3. importandroid.app.Application;
  4. /**
  5. *@authorSodinoE-mail:sodinoopen@hotmail.com
  6. *@versionTime:2011-6-9下午11:49:56
  7. */
  8. publicclassSoftApplicationextendsApplication{
  9. /**"/data/data/<app_package>/files/error.log"*/
  10. publicstaticfinalStringPATH_ERROR_LOG=File.separator+"data"+File.separator+"data"
  11. +File.separator+"lab.sodino.errorreport"+File.separator+"files"+File.separator
  12. +"error.log";
  13. /**标识是否需要退出。为true时表示当前的Activity要执行finish()。*/
  14. privatebooleanneed2Exit;
  15. /**异常处理类。*/
  16. privateUEHandlerueHandler;
  17. publicvoidonCreate(){
  18. need2Exit=false;
  19. ueHandler=newUEHandler(this);
  20. //设置异常处理实例
  21. Thread.setDefaultUncaughtExceptionHandler(ueHandler);
  22. }
  23. publicvoidsetNeed2Exit(booleanbool){
  24. need2Exit=bool;
  25. }
  26. publicbooleanneed2Exit(){
  27. returnneed2Exit;
  28. }
  29. }


lab.sodino.errorreport.ActOccurError.java

view plain
  1. packagelab.sodino.errorreport;
  2. importjava.io.File;
  3. importjava.io.FileInputStream;
  4. importandroid.app.Activity;
  5. importandroid.content.Intent;
  6. importandroid.os.Bundle;
  7. importandroid.util.Log;
  8. importandroid.view.View;
  9. importandroid.widget.Button;
  10. publicclassActOccurErrorextendsActivity{
  11. privateSoftApplicationsoftApplication;
  12. /**Calledwhentheactivityisfirstcreated.*/
  13. @Override
  14. publicvoidonCreate(BundlesavedInstanceState){
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.main);
  17. softApplication=(SoftApplication)getApplication();
  18. //一开始进入程序恢复为"need2Exit=false"。
  19. softApplication.setNeed2Exit(false);
  20. Log.d("ANDROID_LAB","ActOccurError.onCreate()");
  21. ButtonbtnMain=(Button)findViewById(R.id.btnThrowMain);
  22. btnMain.setOnClickListener(newButton.OnClickListener(){
  23. publicvoidonClick(Viewv){
  24. Log.d("ANDROID_LAB","Thread.main.run()");
  25. inti=0;
  26. i=100/i;
  27. }
  28. });
  29. ButtonbtnChild=(Button)findViewById(R.id.btnThrowChild);
  30. btnChild.setOnClickListener(newButton.OnClickListener(){
  31. publicvoidonClick(Viewv){
  32. newThread(){
  33. publicvoidrun(){
  34. Log.d("ANDROID_LAB","Thread.child.run()");
  35. inti=0;
  36. i=100/i;
  37. }
  38. }.start();
  39. }
  40. });
  41. //处理记录于error.log中的异常
  42. StringerrorContent=getErrorLog();
  43. if(errorContent!=null){
  44. Intentintent=newIntent(this,ActErrorReport.class);
  45. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  46. intent.putExtra("error",errorContent);
  47. intent.putExtra("by","error.log");
  48. startActivity(intent);
  49. }
  50. }
  51. publicvoidonStart(){
  52. super.onStart();
  53. if(softApplication.need2Exit()){
  54. Log.d("ANDROID_LAB","ActOccurError.finish()");
  55. ActOccurError.this.finish();
  56. }else{
  57. //donormalthings
  58. }
  59. }
  60. /**
  61. *读取是否有未处理的报错信息。<br/>
  62. *每次读取后都会将error.log清空。<br/>
  63. *
  64. *@return返回未处理的报错信息或null。
  65. */
  66. privateStringgetErrorLog(){
  67. FilefileErrorLog=newFile(SoftApplication.PATH_ERROR_LOG);
  68. Stringcontent=null;
  69. FileInputStreamfis=null;
  70. try{
  71. if(fileErrorLog.exists()){
  72. byte[]data=newbyte[(int)fileErrorLog.length()];
  73. fis=newFileInputStream(fileErrorLog);
  74. fis.read(data);
  75. content=newString(data);
  76. data=null;
  77. }
  78. }catch(Exceptione){
  79. e.printStackTrace();
  80. }finally{
  81. try{
  82. if(fis!=null){
  83. fis.close();
  84. }
  85. if(fileErrorLog.exists()){
  86. fileErrorLog.delete();
  87. }
  88. }catch(Exceptione){
  89. e.printStackTrace();
  90. }
  91. }
  92. returncontent;
  93. }
  94. }

lab.sodino.errorreport.ActErrorReport.java

view plain
  1. packagelab.sodino.errorreport;
  2. importandroid.app.Activity;
  3. importandroid.content.Intent;
  4. importandroid.os.Bundle;
  5. importandroid.util.Log;
  6. importandroid.view.View;
  7. importandroid.widget.Button;
  8. importandroid.widget.EditText;
  9. importandroid.widget.TextView;
  10. /**
  11. *@authorSodinoE-mail:sodinoopen@hotmail.com
  12. *@versionTime:2011-6-12下午01:34:17
  13. */
  14. publicclassActErrorReportextendsActivity{
  15. privateSoftApplicationsoftApplication;
  16. privateStringinfo;
  17. /**标识来处。*/
  18. privateStringby;
  19. privateButtonbtnReport;
  20. privateButtonbtnCancel;
  21. privateBtnListenerbtnListener;
  22. publicvoidonCreate(BundlesavedInstanceState){
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.report);
  25. softApplication=(SoftApplication)getApplication();
  26. by=getIntent().getStringExtra("by");
  27. info=getIntent().getStringExtra("error");
  28. TextViewtxtHint=(TextView)findViewById(R.id.txtErrorHint);
  29. txtHint.setText(getErrorHint(by));
  30. EditTexteditError=(EditText)findViewById(R.id.editErrorContent);
  31. editError.setText(info);
  32. btnListener=newBtnListener();
  33. btnReport=(Button)findViewById(R.id.btnREPORT);
  34. btnCancel=(Button)findViewById(R.id.btnCANCEL);
  35. btnReport.setOnClickListener(btnListener);
  36. btnCancel.setOnClickListener(btnListener);
  37. }
  38. privateStringgetErrorHint(Stringby){
  39. Stringhint="";
  40. Stringappend="";
  41. if("uehandler".equals(by)){
  42. append="whentheapprunning";
  43. }elseif("error.log".equals(by)){
  44. append="whenlasttimetheapprunning";
  45. }
  46. hint=String.format(getResources().getString(R.string.errorHint),append,1);
  47. returnhint;
  48. }
  49. publicvoidonStart(){
  50. super.onStart();
  51. if(softApplication.need2Exit()){
  52. //上一个退栈的Activity有执行“退出”的操作。
  53. Log.d("ANDROID_LAB","ActErrorReport.finish()");
  54. ActErrorReport.this.finish();
  55. }else{
  56. //goaheadnormally
  57. }
  58. }
  59. classBtnListenerimplementsButton.OnClickListener{
  60. @Override
  61. publicvoidonClick(Viewv){
  62. if(v==btnReport){
  63. //需要android.permission.SEND权限
  64. IntentmailIntent=newIntent(Intent.ACTION_SEND);
  65. mailIntent.setType("plain/text");
  66. String[]arrReceiver={"sodinoopen@hotmail.com"};
  67. StringmailSubject="AppErrorInfo["+getPackageName()+"]";
  68. StringmailBody=info;
  69. mailIntent.putExtra(Intent.EXTRA_EMAIL,arrReceiver);
  70. mailIntent.putExtra(Intent.EXTRA_SUBJECT,mailSubject);
  71. mailIntent.putExtra(Intent.EXTRA_TEXT,mailBody);
  72. startActivity(Intent.createChooser(mailIntent,"MailSending..."));
  73. ActErrorReport.this.finish();
  74. }elseif(v==btnCancel){
  75. ActErrorReport.this.finish();
  76. }
  77. }
  78. }
  79. publicvoidfinish(){
  80. super.finish();
  81. if("error.log".equals(by)){
  82. //donothing
  83. }elseif("uehandler".equals(by)){
  84. //1.
  85. //android.os.Process.killProcess(android.os.Process.myPid());
  86. //2.
  87. //ActivityManageram=(ActivityManager)
  88. //getSystemService(ACTIVITY_SERVICE);
  89. //am.restartPackage("lab.sodino.errorreport");
  90. //3.
  91. //System.exit(0);
  92. //1.2.3.都失效了,Google你让悲催的程序员情何以堪啊。
  93. softApplication.setNeed2Exit(true);
  94. //////////////////////////////////////////////////////
  95. ////另一个替换方案是直接返回“HOME”
  96. //Intenti=newIntent(Intent.ACTION_MAIN);
  97. ////如果是服务里调用,必须加入newtask标识
  98. //i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  99. //i.addCategory(Intent.CATEGORY_HOME);
  100. //startActivity(i);
  101. //////////////////////////////////////////////////////
  102. }
  103. }
  104. }

lab.sodino.errorreport.UEHandler.java

view plain
  1. packagelab.sodino.errorreport;
  2. importjava.io.ByteArrayOutputStream;
  3. importjava.io.File;
  4. importjava.io.FileOutputStream;
  5. importjava.io.PrintStream;
  6. importandroid.content.Intent;
  7. importandroid.util.Log;
  8. /**
  9. *@authorSodinoE-mail:sodinoopen@hotmail.com
  10. *@versionTime:2011-6-9下午11:50:43
  11. */
  12. publicclassUEHandlerimplementsThread.UncaughtExceptionHandler{
  13. privateSoftApplicationsoftApp;
  14. privateFilefileErrorLog;
  15. publicUEHandler(SoftApplicationapp){
  16. softApp=app;
  17. fileErrorLog=newFile(SoftApplication.PATH_ERROR_LOG);
  18. }
  19. @Override
  20. publicvoiduncaughtException(Threadthread,Throwableex){
  21. //fetchExcpetionInfo
  22. Stringinfo=null;
  23. ByteArrayOutputStreambaos=null;
  24. PrintStreamprintStream=null;
  25. try{
  26. baos=newByteArrayOutputStream();
  27. printStream=newPrintStream(baos);
  28. ex.printStackTrace(printStream);
  29. byte[]data=baos.toByteArray();
  30. info=newString(data);
  31. data=null;
  32. }catch(Exceptione){
  33. e.printStackTrace();
  34. }finally{
  35. try{
  36. if(printStream!=null){
  37. printStream.close();
  38. }
  39. if(baos!=null){
  40. baos.close();
  41. }
  42. }catch(Exceptione){
  43. e.printStackTrace();
  44. }
  45. }
  46. //print
  47. longthreadId=thread.getId();
  48. Log.d("ANDROID_LAB","Thread.getName()="+thread.getName()+"id="+threadId+"state="
  49. +thread.getState());
  50. Log.d("ANDROID_LAB","Error["+info+"]");
  51. if(threadId!=1){
  52. //对于非UI线程可显示出提示界面,如果是UI线程抛的异常则界面卡死直到ANR。
  53. Intentintent=newIntent(softApp,ActErrorReport.class);
  54. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  55. intent.putExtra("error",info);
  56. intent.putExtra("by","uehandler");
  57. softApp.startActivity(intent);
  58. }else{
  59. //write2/data/data/<app_package>/files/error.log
  60. write2ErrorLog(fileErrorLog,info);
  61. //killAppProgress
  62. android.os.Process.killProcess(android.os.Process.myPid());
  63. }
  64. }
  65. privatevoidwrite2ErrorLog(Filefile,Stringcontent){
  66. FileOutputStreamfos=null;
  67. try{
  68. if(file.exists()){
  69. //清空之前的记录
  70. file.delete();
  71. }else{
  72. file.getParentFile().mkdirs();
  73. }
  74. file.createNewFile();
  75. fos=newFileOutputStream(file);
  76. fos.write(content.getBytes());
  77. }catch(Exceptione){
  78. e.printStackTrace();
  79. }finally{
  80. try{
  81. if(fos!=null){
  82. fos.close();
  83. }
  84. }catch(Exceptione){
  85. e.printStackTrace();
  86. }
  87. }
  88. }
  89. }


/res/layout/main.xml

view plain
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <TextView
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="@string/hello"
  11. />
  12. <Buttonandroid:layout_width="fill_parent"
  13. android:layout_height="wrap_content"
  14. android:text="ThrowsExceptionByMainThread"
  15. android:id="@+id/btnThrowMain"
  16. ></Button>
  17. <Buttonandroid:layout_width="fill_parent"
  18. android:layout_height="wrap_content"
  19. android:text="ThrowsExceptionByChildThread"
  20. android:id="@+id/btnThrowChild"
  21. ></Button>
  22. </LinearLayout>

/res/layout/report.xml

view plain
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <TextViewandroid:layout_width="fill_parent"
  6. android:layout_height="wrap_content"
  7. android:text="@string/errorHint"
  8. android:id="@+id/txtErrorHint"/>
  9. <EditTextandroid:layout_width="fill_parent"
  10. android:layout_height="wrap_content"android:id="@+id/editErrorContent"
  11. android:editable="false"android:layout_weight="1"></EditText>
  12. <LinearLayoutandroid:layout_width="fill_parent"
  13. android:layout_height="wrap_content"android:background="#96cdcd"
  14. android:gravity="center"android:orientation="horizontal">
  15. <Buttonandroid:layout_width="fill_parent"
  16. android:layout_height="wrap_content"android:text="Report"
  17. android:id="@+id/btnREPORT"android:layout_weight="1"></Button>
  18. <Buttonandroid:layout_width="fill_parent"
  19. android:layout_height="wrap_content"android:text="Cancel"
  20. android:id="@+id/btnCANCEL"android:layout_weight="1"></Button>
  21. </LinearLayout>
  22. </LinearLayout>

用到的string.xml资源为:

view plain
  1. <stringname="errorHint">Aerrorhashappened%1$s.Pleaseclick<i><b>"REPORT"</b></i>tosendtheerrorinformationtousbyemail,Thanks!!!</string>


重要的一点是要在AndroidManifest.xml中对<application>节点设置android:name=".SoftApplication"

本文内容归CSDN博客博主Sodino 所有
转载请注明出处:http://blog.csdn.net/sodino/archive/2011/06/13/6540329.aspx




更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. android中常见的内存泄漏和解决办法
  3. Android(安卓)利用SurfaceView实现一个简单的计时器
  4. 随笔之如何判断Android应用进程是否为单线程及闲扯多核并行编程
  5. android 上企业信息云端查看及实现
  6. Android(安卓)玩家看过来:来自 Seagate 的 GoFlex Satellite 你也
  7. 浅谈android的线程安全和handler处理
  8. 在电脑端加密的文件,放在android上进行解密,但是出现pad block cor
  9. Android最佳性能实践(1):合理管理内存

随机推荐

  1. appium启动APP配置参数:
  2. android获取屏幕信息
  3. Android(安卓)Cursor遍历
  4. Activity切换 窗口绘制显示
  5. [Android实例] android多点触摸demo
  6. Android在Gallery中每次滑动只显示一页
  7. 【原创】android webview 加载网络视频
  8. android 加下划线
  9. android之sax解析xml文件
  10. Android(安卓)学习--ListView 的使用(三)