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

来源:互联网 发布:多益java面试 编辑:程序博客网 时间:2024/06/05 05:30

网址 http://www.apkbus.com/android-4209-1-1.html


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

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

效果图如下[Devdiv]:
1.gif
过程了解了,则需要了解的几个知识点如下:
1.拦截UncaughtException
Application.onCreate()是整个Android应用的入口方法。在该方法中执行如下代码即可拦截UncaughtException:
  1.   ueHandler = new UEHandler(this);
  2.                 // 设置异常处理实例
  3.                 Thread.setDefaultUncaughtExceptionHandler(ueHandler);
复制代码


2.抓取导致程序崩溃的异常信息
UEHandler是Thread.UncaughtExceptionHandler的实现类,在其public void uncaughtException(Thread thread, Throwable ex)的实现中可以获取崩溃信息,代码如下:
  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
  1. package lab.sodino.errorreport;
  2. import java.io.File;
  3. import android.app.Application;
  4. /**
  5. * @author Sodino E-mail:sodinoopen@hotmail.com
  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
  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
  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:sodinoopen@hotmail.com
  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 = { "sodinoopen@hotmail.com" };
  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
  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:sodinoopen@hotmail.com
  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
  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
  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资源为:
<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"



原创粉丝点击