APP的异常捕获

时间:2021-11-28 08:50:48

在APP开发过程当中,会遇到很多异常导致程序崩溃,我们,很希望把这些异常记录下来方便我们修改

这些崩溃,或者异常信息,所有的这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照 Android 中使用 HTTP 服务相关介绍。
不过在使用 HTTP 服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:


在应用运行过程中,有很多异常可能会发生,而我们希望在异常发生的时候第一时间的保存现场。

如何处理未捕获的异常呢?

首先我们要实现一个接口  java.lang.Thread.UncaughtExceptionHandler    ,要实现该接口里面的  uncaughtException(Thread t, Throwable e) ,在这个函数里面,我们可以做一些处理。例如将异常信息保存到sdcard上的某个位置,或者提示用户异常出现等等一些操作。

我们在进入Activity的onCreate函数的时候,设置一下处理未捕获异常 

在Application的,onCreate中,初始化自定义的CrashHandler

[java] view plain copy
  1. import android.app.Application;  
  2.   
  3. import com.tjd.appexceptioncatch.exception.CrashHandler;  
  4.   
  5. public class MyApplication extends Application {  
  6.     private static MyApplication instance;  
  7.     @Override  
  8.     public void onCreate() {  
  9.         super.onCreate();  
  10.         CrashHandler.getInstance().init(getApplicationContext());  
  11.     }  
  12.     public static MyApplication getInstance() {  
  13.         if (instance == null) {  
  14.             instance = new MyApplication();  
  15.         }  
  16.         return instance;  
  17.     }  
  18. }  
自定义CrashHandler如下

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

[java] view plain copy
  1. import android.app.Activity;  
  2. import android.os.Bundle;  
  3.   
  4. public class MainActivity extends Activity {  
  5.     final String str = null;  
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.activity_main);  
  10.         str.equals("exception");  
  11.     }  
  12. }  
本地项目名备忘:AppExceptionCatch

参考原文链接:http://blog.csdn.net/ytjd926/article/details/10004593