Android 对程序异常崩溃的捕捉

时间:2022-04-13 06:42:58
[java] view plaincopyprint?
  1. 以下为异常捕捉处理代码:  
[java] view plaincopyprint?
  1. import java.io.BufferedReader;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStreamReader;  
  8. import java.io.PrintWriter;  
  9. import java.io.StringWriter;  
  10. import java.io.Writer;  
  11. import java.lang.Thread.UncaughtExceptionHandler;  
  12. import java.lang.reflect.Field;  
  13. import java.text.DateFormat;  
  14. import java.text.SimpleDateFormat;  
  15. import java.util.Date;  
  16. import java.util.HashMap;  
  17. import java.util.Map;  
  18.   
  19. import android.content.Context;  
  20. import android.content.pm.PackageInfo;  
  21. import android.content.pm.PackageManager;  
  22. import android.content.pm.PackageManager.NameNotFoundException;  
  23. import android.os.Build;  
  24. import android.os.Environment;  
  25. import android.os.Looper;  
  26. import android.util.Log;  
  27. import android.widget.Toast;  
  28.       
  29. /**   
  30.  * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.  
  31.  *  
  32.  *  需要在Application中注册,为了要在程序启动器就监控整个程序。 
  33.  */      
  34. public class CrashHandler implements UncaughtExceptionHandler {      
  35.           
  36.     public static final String TAG = "CrashHandler";      
  37.           
  38.     //系统默认的UncaughtException处理类       
  39.     private Thread.UncaughtExceptionHandler mDefaultHandler;      
  40.     //CrashHandler实例      
  41.     private static CrashHandler instance;  
  42.    //程序的Context对象      
  43.     private Context mContext;      
  44.     //用来存储设备信息和异常信息      
  45.     private Map<String, String> infos = new HashMap<String, String>();      
  46.       
  47.     //用于格式化日期,作为日志文件名的一部分      
  48.     private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");      
  49.       
  50.     /** 保证只有一个CrashHandler实例 */      
  51.     private CrashHandler() {}      
  52.       
  53.     /** 获取CrashHandler实例 ,单例模式 */      
  54.     public static CrashHandler getInstance() {      
  55.         if(instance == null)  
  56.             instance = new CrashHandler();     
  57.         return instance;      
  58.     }      
  59.       
  60.     /**   
  61.      * 初始化   
  62.      */      
  63.     public void init(Context context) {      
  64.         mContext = context;      
  65.         //获取系统默认的UncaughtException处理器      
  66.         mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();      
  67.         //设置该CrashHandler为程序的默认处理器      
  68.         Thread.setDefaultUncaughtExceptionHandler(this);      
  69.     }      
  70.       
  71.     /**   
  72.      * 当UncaughtException发生时会转入该函数来处理   
  73.      */      
  74.     @Override      
  75.     public void uncaughtException(Thread thread, Throwable ex) {      
  76.         if (!handleException(ex) && mDefaultHandler != null) {      
  77.             //如果用户没有处理则让系统默认的异常处理器来处理      
  78.             mDefaultHandler.uncaughtException(thread, ex);      
  79.         } else {      
  80.             try {      
  81.                 Thread.sleep(3000);      
  82.             } catch (InterruptedException e) {      
  83.                 Log.e(TAG, "error : ", e);      
  84.             }      
  85.             //退出程序      
  86.             android.os.Process.killProcess(android.os.Process.myPid());      
  87.             System.exit(1);      
  88.         }      
  89.     }      
  90.       
  91.     /**   
  92.      * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.   
  93.      *    
  94.      * @param ex   
  95.      * @return true:如果处理了该异常信息;否则返回false.   
  96.      */      
  97.     private boolean handleException(Throwable ex) {      
  98.         if (ex == null) {      
  99.             return false;      
  100.         }      
  101.         //收集设备参数信息       
  102.         collectDeviceInfo(mContext);      
  103.           
  104.         //使用Toast来显示异常信息      
  105.         new Thread() {      
  106.             @Override      
  107.             public void run() {      
  108.                 Looper.prepare();      
  109.                 Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();      
  110.                 Looper.loop();      
  111.             }      
  112.         }.start();      
  113.         //保存日志文件       
  114.         saveCatchInfo2File(ex);    
  115.         return true;      
  116.     }      
  117.           
  118.     /**   
  119.      * 收集设备参数信息   
  120.      * @param ctx   
  121.      */      
  122.     public void collectDeviceInfo(Context ctx) {      
  123.         try {      
  124.             PackageManager pm = ctx.getPackageManager();      
  125.             PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);      
  126.             if (pi != null) {      
  127.                 String versionName = pi.versionName == null ? "null" : pi.versionName;      
  128.                 String versionCode = pi.versionCode + "";      
  129.                 infos.put("versionName", versionName);      
  130.                 infos.put("versionCode", versionCode);      
  131.             }      
  132.         } catch (NameNotFoundException e) {      
  133.             Log.e(TAG, "an error occured when collect package info", e);      
  134.         }      
  135.         Field[] fields = Build.class.getDeclaredFields();      
  136.         for (Field field : fields) {      
  137.             try {      
  138.                 field.setAccessible(true);      
  139.                 infos.put(field.getName(), field.get(null).toString());      
  140.                 Log.d(TAG, field.getName() + " : " + field.get(null));      
  141.             } catch (Exception e) {      
  142.                 Log.e(TAG, "an error occured when collect crash info", e);      
  143.             }      
  144.         }      
  145.     }      
  146.       
  147.     /**   
  148.      * 保存错误信息到文件中   
  149.      *    
  150.      * @param ex   
  151.      * @return  返回文件名称,便于将文件传送到服务器   
  152.      */      
  153.     private String saveCatchInfo2File(Throwable ex) {      
  154.               
  155.         StringBuffer sb = new StringBuffer();      
  156.         for (Map.Entry<String, String> entry : infos.entrySet()) {      
  157.             String key = entry.getKey();      
  158.             String value = entry.getValue();      
  159.             sb.append(key + "=" + value + "\n");      
  160.         }      
  161.               
  162.         Writer writer = new StringWriter();      
  163.         PrintWriter printWriter = new PrintWriter(writer);      
  164.         ex.printStackTrace(printWriter);      
  165.         Throwable cause = ex.getCause();      
  166.         while (cause != null) {      
  167.             cause.printStackTrace(printWriter);      
  168.             cause = cause.getCause();      
  169.         }      
  170.         printWriter.close();      
  171.         String result = writer.toString();      
  172.         sb.append(result);      
  173.         try {      
  174.             long timestamp = System.currentTimeMillis();      
  175.             String time = formatter.format(new Date());      
  176.             String fileName = "crash-" + time + "-" + timestamp + ".log";      
  177.             if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {      
  178.                 String path = "/mnt/sdcard/crash/";      
  179.                 File dir = new File(path);      
  180.                 if (!dir.exists()) {      
  181.                     dir.mkdirs();      
  182.                 }      
  183.                 FileOutputStream fos = new FileOutputStream(path + fileName);      
  184.                 fos.write(sb.toString().getBytes());    
  185.                 //发送给开发人员  
  186.                 sendCrashLog2PM(path+fileName);  
  187.                 fos.close();      
  188.             }      
  189.             return fileName;      
  190.         } catch (Exception e) {      
  191.             Log.e(TAG, "an error occured while writing file...", e);      
  192.         }      
  193.         return null;      
  194.     }      
  195.       
  196.     /** 
  197.      * 将捕获的导致崩溃的错误信息发送给开发人员 
  198.      *  
  199.      * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。 
  200.      */  
  201.     private void sendCrashLog2PM(String fileName){  
  202.         if(!new File(fileName).exists()){  
  203.             Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();  
  204.             return;  
  205.         }  
  206.         FileInputStream fis = null;  
  207.         BufferedReader reader = null;  
  208.         String s = null;  
  209.         try {  
  210.             fis = new FileInputStream(fileName);  
  211.             reader = new BufferedReader(new InputStreamReader(fis, "GBK"));  
  212.             while(true){  
  213.                 s = reader.readLine();  
  214.                 if(s == nullbreak;  
  215.                 //由于目前尚未确定以何种方式发送,所以先打出log日志。  
  216.                 Log.i("info", s.toString());  
  217.             }  
  218.         } catch (FileNotFoundException e) {  
  219.             e.printStackTrace();  
  220.         } catch (IOException e) {  
  221.             e.printStackTrace();  
  222.         }finally{   // 关闭流  
  223.             try {  
  224.                 reader.close();  
  225.                 fis.close();  
  226.             } catch (IOException e) {  
  227.                 e.printStackTrace();  
  228.             }  
  229.         }  
  230.     }  
  231. }      


针对异常的捕捉要进行全局监控整个项目,所以要将其在Application中注册(也就是初始化):

[java] view plaincopyprint?
  1. import android.app.Application;  
  2.   
  3. public class CrashApplication extends Application {  
  4.   
  5.     @Override  
  6.     public void onCreate() {  
  7.         super.onCreate();  
  8.         CrashHandler catchHandler = CrashHandler.getInstance();  
  9.         catchHandler.init(getApplicationContext());  
  10.     }  
  11. }  

现在模拟一个空指针异常:

[java] view plaincopyprint?
  1. import android.app.Activity;  
  2. import android.os.Bundle;  
  3.   
  4. public class CatchExceptionLogActivity extends Activity {  
  5.     /** Called when the activity is first created. */  
  6.     private String s;  
  7.     @Override  
  8.     public void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.main);  
  11.         System.out.println(s.equals("hello"));  // s没有进行赋值,所以会出现NullPointException异常  
  12.     }  
  13. }  

别忘了在配置文件中对Application进行注册:

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.forms.catchlog"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk android:minSdkVersion="8" />  
  8.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
  9.       
  10.   
  11.     <application  
  12.         android:icon="@drawable/ic_launcher"  
  13.         android:label="@string/app_name"   
  14.         <span style="color:#ff0000;">android:name=".CrashApplication"></span>  
  15.         <activity  
  16.             android:label="@string/app_name"  
  17.             android:name=".CatchExceptionLogActivity" >  
  18.             <intent-filter >  
  19.                 <action android:name="android.intent.action.MAIN" />  
  20.   
  21.                 <category android:name="android.intent.category.LAUNCHER" />  
  22.             </intent-filter>  
  23.         </activity>  
  24.     </application>  
  25.   
  26. </manifest>  

添加异常捕获之后的日志提示

由于Android设备各异,第三方定制的Android系统也非常多,我们不可能对所有的设备场景都进行测试,因而开发一款完全无bug的应用几乎是不可能的任务,那么当应用在用户的设备上Force Close时,我们是不是可以捕获这个错误,记录用户的设备信息,然后让用户选择是否反馈这些堆栈信息,通过这种bug反馈方式,我们可以有针对性地对bug进行修复。

当我们的的应用由于运行时异常导致Force Close的时候,可以设置主线程的UncaughtExceptionHandler,实现捕获运行时异常的堆栈信息。同时用户可以把堆栈信息通过发送邮件的方式反馈给我们。下面是实现的代码:

代码下载请按此

例子:点击按钮后,会触发一个NullPointerException的运行时异常,这个例子实现了捕获运行时异常,发送邮件反馈设备和堆栈信息的功能。

界面1(触发运行时异常)

Android 对程序异常崩溃的捕捉
界面2(发送堆栈信息)

 Android 对程序异常崩溃的捕捉

TestActivity.java

view plain
package com.zhuozhuo; 
 
import java.io.PrintWriter; 
import java.io.StringWriter; 
import java.lang.Thread.UncaughtExceptionHandler; 
 
import android.app.Activity; 
import android.content.Intent; 
import android.net.Uri; 
import android.os.Build; 
import android.os.Bundle; 
import android.util.Log; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.EditText; 
import android.widget.TextView; 
 
public class TestActivity extends Activity { 
    /** Called when the activity is first created. */ 
 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {//给主线程设置一个处理运行时异常的handler 
 
            @Override 
            public void uncaughtException(Thread thread, final Throwable ex) { 
 
                StringWriter sw = new StringWriter(); 
                PrintWriter pw = new PrintWriter(sw); 
                ex.printStackTrace(pw); 
                 
                StringBuilder sb = new StringBuilder(); 
                 
                sb.append("Version code is "); 
                sb.append(Build.VERSION.SDK_INT + "\n");//设备的Android版本号 
                sb.append("Model is "); 
                sb.append(Build.MODEL+"\n");//设备型号 
                sb.append(sw.toString()); 
 
                Intent sendIntent = new Intent(Intent.ACTION_SENDTO); 
                sendIntent.setData(Uri.parse("mailto:csdn@csdn.com"));//发送邮件异常到csdn@csdn.com邮箱 
                sendIntent.putExtra(Intent.EXTRA_SUBJECT, "bug report");//邮件主题 
                sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString());//堆栈信息 
                startActivity(sendIntent); 
                finish(); 
            } 
        }); 
         
        findViewById(R.id.button).setOnClickListener(new OnClickListener() { 
             
            @Override 
            public void onClick(View v) { 
                Integer a = null; 
                a.toString();//触发nullpointer运行时错误 
                 
            } 
        }); 
         
    } 

main.xml
view plain
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
    android:orientation="vertical" android:layout_width="fill_parent" 
    android:layout_height="fill_parent"> 
    <TextView android:layout_width="fill_parent" 
        android:layout_height="wrap_content" android:text="@string/hello" /> 
    <EditText android:id="@+id/editText1" android:layout_width="match_parent" 
        android:text="点击按钮触发运行时异常" android:layout_height="wrap_content" 
        android:layout_weight="1" android:gravity="top"></EditText> 
    <Button android:text="按钮" android:id="@+id/button
        android:layout_width="wrap_content" android:layout_height="wrap_content" 
        android:layout_gravity="center_horizontal"></Button> 
</LinearLayout>