[Android]将应用崩溃信息汇报给开发者

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

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

效果图如下[CSDN]:

[Android]将应用崩溃信息汇报给开发者_第1张图片 
过程了解了,则需要了解的几个知识点如下:
1.拦截UncaughtException
Application.onCreate()是整个Android应用的入口方法。在该方法中执行如下代码即可拦截UncaughtException:

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

 

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

view plain
  1. // fetch Excpetion Info  
  2. String info = null;  
  3. ByteArrayOutputStream baos = null;  
  4. PrintStream printStream = null;  
  5. try {  
  6.     baos = new ByteArrayOutputStream();  
  7.     printStream = new PrintStream(baos);  
  8.     ex.printStackTrace(printStream);  
  9.     byte[] data = baos.toByteArray();  
  10.     info = new String(data);  
  11.     data = null;  
  12. catch (Exception e) {  
  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 (Exception e) {  
  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. package lab.sodino.errorreport;  
  2. import java.io.File;  
  3. import android.app.Application;  
  4. /** 
  5.  * @author Sodino E-mail:[email protected] 
  6.  * @version Time:2011-6-9 下午11:49:56 
  7.  */  
  8. public class SoftApplication extends Application {  
  9.     /** "/data/data/<app_package>/files/error.log" */  
  10.     public static final String PATH_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.     private boolean need2Exit;  
  15.     /** 异常处理类。 */  
  16.     private UEHandler ueHandler;  
  17.     public void onCreate() {  
  18.         need2Exit = false;  
  19.         ueHandler = new UEHandler(this);  
  20.         // 设置异常处理实例  
  21.         Thread.setDefaultUncaughtExceptionHandler(ueHandler);  
  22.     }  
  23.     public void setNeed2Exit(boolean bool) {  
  24.         need2Exit = bool;  
  25.     }  
  26.     public boolean need2Exit() {  
  27.         return need2Exit;  
  28.     }  
  29. }  

lab.sodino.errorreport.ActOccurError.java

view plain
  1. package lab.sodino.errorreport;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import android.app.Activity;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. public class ActOccurError extends Activity {  
  11.     private SoftApplication softApplication;  
  12.     /** Called when the activity is first created. */  
  13.     @Override  
  14.     public void onCreate(Bundle savedInstanceState) {  
  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.         Button btnMain = (Button) findViewById(R.id.btnThrowMain);  
  22.         btnMain.setOnClickListener(new Button.OnClickListener() {  
  23.             public void onClick(View v) {  
  24.                 Log.d("ANDROID_LAB""Thread.main.run()");  
  25.                 int i = 0;  
  26.                 i = 100 / i;  
  27.             }  
  28.         });  
  29.         Button btnChild = (Button) findViewById(R.id.btnThrowChild);  
  30.         btnChild.setOnClickListener(new Button.OnClickListener() {  
  31.             public void onClick(View v) {  
  32.                 new Thread() {  
  33.                     public void run() {  
  34.                         Log.d("ANDROID_LAB""Thread.child.run()");  
  35.                         int i = 0;  
  36.                         i = 100 / i;  
  37.                     }  
  38.                 }.start();  
  39.             }  
  40.         });  
  41.         // 处理记录于error.log中的异常  
  42.         String errorContent = getErrorLog();  
  43.         if (errorContent != null) {  
  44.             Intent intent = new Intent(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.     public void onStart() {  
  52.         super.onStart();  
  53.         if (softApplication.need2Exit()) {  
  54.             Log.d("ANDROID_LAB""ActOccurError.finish()");  
  55.             ActOccurError.this.finish();  
  56.         } else {  
  57.             // do normal things  
  58.         }  
  59.     }  
  60.     /** 
  61.      * 读取是否有未处理的报错信息。<br/> 
  62.      * 每次读取后都会将error.log清空。<br/> 
  63.      *  
  64.      * @return 返回未处理的报错信息或null。 
  65.      */  
  66.     private String getErrorLog() {  
  67.         File fileErrorLog = new File(SoftApplication.PATH_ERROR_LOG);  
  68.         String content = null;  
  69.         FileInputStream fis = null;  
  70.         try {  
  71.             if (fileErrorLog.exists()) {  
  72.                 byte[] data = new byte[(int) fileErrorLog.length()];  
  73.                 fis = new FileInputStream(fileErrorLog);  
  74.                 fis.read(data);  
  75.                 content = new String(data);  
  76.                 data = null;  
  77.             }  
  78.         } catch (Exception e) {  
  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 (Exception e) {  
  89.                 e.printStackTrace();  
  90.             }  
  91.         }  
  92.         return content;  
  93.     }  
  94. }  

lab.sodino.errorreport.ActErrorReport.java

view plain
  1. package lab.sodino.errorreport;  
  2. import android.app.Activity;  
  3. import android.content.Intent;  
  4. import android.os.Bundle;  
  5. import android.util.Log;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. import android.widget.EditText;  
  9. import android.widget.TextView;  
  10. /** 
  11.  * @author Sodino E-mail:[email protected] 
  12.  * @version Time:2011-6-12 下午01:34:17 
  13.  */  
  14. public class ActErrorReport extends Activity {  
  15.     private SoftApplication softApplication;  
  16.     private String info;  
  17.     /** 标识来处。 */  
  18.     private String by;  
  19.     private Button btnReport;  
  20.     private Button btnCancel;  
  21.     private BtnListener btnListener;  
  22.     public void onCreate(Bundle savedInstanceState) {  
  23.         super.onCreate(savedInstanceState);  
  24.         setContentView(R.layout.report);  
  25.         softApplication = (SoftApplication) getApplication();  
  26.         by = getIntent().getStringExtra("by");  
  27.         info = getIntent().getStringExtra("error");  
  28.         TextView txtHint = (TextView) findViewById(R.id.txtErrorHint);  
  29.         txtHint.setText(getErrorHint(by));  
  30.         EditText editError = (EditText) findViewById(R.id.editErrorContent);  
  31.         editError.setText(info);  
  32.         btnListener = new BtnListener();  
  33.         btnReport = (Button) findViewById(R.id.btnREPORT);  
  34.         btnCancel = (Button) findViewById(R.id.btnCANCEL);  
  35.         btnReport.setOnClickListener(btnListener);  
  36.         btnCancel.setOnClickListener(btnListener);  
  37.     }  
  38.     private String getErrorHint(String by) {  
  39.         String hint = "";  
  40.         String append = "";  
  41.         if ("uehandler".equals(by)) {  
  42.             append = " when the app running";  
  43.         } else if ("error.log".equals(by)) {  
  44.             append = " when last time the app running";  
  45.         }  
  46.         hint = String.format(getResources().getString(R.string.errorHint), append, 1);  
  47.         return hint;  
  48.     }  
  49.     public void onStart() {  
  50.         super.onStart();  
  51.         if (softApplication.need2Exit()) {  
  52.             // 上一个退栈的Activity有执行“退出”的操作。  
  53.             Log.d("ANDROID_LAB""ActErrorReport.finish()");  
  54.             ActErrorReport.this.finish();  
  55.         } else {  
  56.             // go ahead normally  
  57.         }  
  58.     }  
  59.     class BtnListener implements Button.OnClickListener {  
  60.         @Override  
  61.         public void onClick(View v) {  
  62.             if (v == btnReport) {  
  63.                 // 需要 android.permission.SEND权限  
  64.                 Intent mailIntent = new Intent(Intent.ACTION_SEND);  
  65.                 mailIntent.setType("plain/text");  
  66.                 String[] arrReceiver = { "[email protected]" };  
  67.                 String mailSubject = "App Error Info[" + getPackageName() + "]";  
  68.                 String mailBody = 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, "Mail Sending..."));  
  73.                 ActErrorReport.this.finish();  
  74.             } else if (v == btnCancel) {  
  75.                 ActErrorReport.this.finish();  
  76.             }  
  77.         }  
  78.     }  
  79.     public void finish() {  
  80.         super.finish();  
  81.         if ("error.log".equals(by)) {  
  82.             // do nothing  
  83.         } else if ("uehandler".equals(by)) {  
  84.             // 1.  
  85.             // android.os.Process.killProcess(android.os.Process.myPid());  
  86.             // 2.  
  87.             // ActivityManager am = (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.             // Intent i = new Intent(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. package lab.sodino.errorreport;  
  2. import java.io.ByteArrayOutputStream;  
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.PrintStream;  
  6. import android.content.Intent;  
  7. import android.util.Log;  
  8. /** 
  9.  * @author Sodino E-mail:[email protected] 
  10.  * @version Time:2011-6-9 下午11:50:43 
  11.  */  
  12. public class UEHandler implements Thread.UncaughtExceptionHandler {  
  13.     private SoftApplication softApp;  
  14.     private File fileErrorLog;  
  15.     public UEHandler(SoftApplication app) {  
  16.         softApp = app;  
  17.         fileErrorLog = new File(SoftApplication.PATH_ERROR_LOG);  
  18.     }  
  19.     @Override  
  20.     public void uncaughtException(Thread thread, Throwable ex) {  
  21.         // fetch Excpetion Info  
  22.         String info = null;  
  23.         ByteArrayOutputStream baos = null;  
  24.         PrintStream printStream = null;  
  25.         try {  
  26.             baos = new ByteArrayOutputStream();  
  27.             printStream = new PrintStream(baos);  
  28.             ex.printStackTrace(printStream);  
  29.             byte[] data = baos.toByteArray();  
  30.             info = new String(data);  
  31.             data = null;  
  32.         } catch (Exception e) {  
  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 (Exception e) {  
  43.                 e.printStackTrace();  
  44.             }  
  45.         }  
  46.         // print  
  47.         long threadId = 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.             Intent intent = new Intent(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.             // write 2 /data/data/<app_package>/files/error.log  
  60.             write2ErrorLog(fileErrorLog, info);  
  61.             // kill App Progress  
  62.             android.os.Process.killProcess(android.os.Process.myPid());  
  63.         }  
  64.     }  
  65.     private void write2ErrorLog(File file, String content) {  
  66.         FileOutputStream fos = null;  
  67.         try {  
  68.             if (file.exists()) {  
  69.                 // 清空之前的记录  
  70.                 file.delete();  
  71.             } else {  
  72.                 file.getParentFile().mkdirs();  
  73.             }  
  74.             file.createNewFile();  
  75.             fos = new FileOutputStream(file);  
  76.             fos.write(content.getBytes());  
  77.         } catch (Exception e) {  
  78.             e.printStackTrace();  
  79.         } finally {  
  80.             try {  
  81.                 if (fos != null) {  
  82.                     fos.close();  
  83.                 }  
  84.             } catch (Exception e) {  
  85.                 e.printStackTrace();  
  86.             }  
  87.         }  
  88.     }  
  89. }  

 

/res/layout/main.xml

view plain
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns: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.     <Button android:layout_width="fill_parent"  
  13.         android:layout_height="wrap_content"  
  14.         android:text="Throws Exception By Main Thread"  
  15.         android:id="@+id/btnThrowMain"  
  16.     ></Button>  
  17.     <Button android:layout_width="fill_parent"  
  18.         android:layout_height="wrap_content"  
  19.         android:text="Throws Exception By Child Thread"  
  20.         android:id="@+id/btnThrowChild"  
  21.     ></Button>  
  22. </LinearLayout>  

/res/layout/report.xml

view plain
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical" android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent">  
  5.     <TextView android:layout_width="fill_parent"  
  6.         android:layout_height="wrap_content"  
  7.         android:text="@string/errorHint"  
  8.         android:id="@+id/txtErrorHint" />  
  9.     <EditText android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content" android:id="@+id/editErrorContent"  
  11.         android:editable="false" android:layout_weight="1"></EditText>  
  12.     <LinearLayout android:layout_width="fill_parent"  
  13.         android:layout_height="wrap_content" android:background="#96cdcd"  
  14.         android:gravity="center" android:orientation="horizontal">  
  15.         <Button android:layout_width="fill_parent"  
  16.             android:layout_height="wrap_content" android:text="Report"  
  17.             android:id="@+id/btnREPORT" android:layout_weight="1"></Button>  
  18.         <Button android: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. <string name="errorHint">A error has happened %1$s.Please click <i><b>"REPORT"</b></i> to send the error information to us by email, Thanks!!!</string>  


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

你可能感兴趣的:([Android]将应用崩溃信息汇报给开发者)