【Android】Android Crash之异常信息反馈机制

转自http://blog.csdn.net/u010119170/article/details/38236991

1.为什么需要反馈Crash报告?

       Crash最通俗直观的感受就是App软件出现崩溃导致的闪退等现象,在Android原生态下会出现一个App Force Close的Dialog,但是对于用户体验相当不好。Crash的产生是不可避免的,它产生的原因可能来自于Android底层的Bug,或是因为网络不畅,又或者是手机适配性问题,更严重的是代码质量不过关。Crash的产生是我们最不愿意看到的,即使我们花费大量时间进行测试,测试,再测试,bug还是会产生,为了能够获取到Crash信息,正式的软件中都会有Crash的反馈机制,开发人员会根据这些Crash信息对软件进行改进。

2.如何获取这些Crash信息?

      1)首先,先看这样一个方法

  1. /** 
  2.  * Sets the default uncaught exception handler. This handler is invoked in 
  3.  * case any Thread dies due to an unhandled exception. 
  4.  * 
  5.  * @param handler 
  6.  *            The handler to set or null. 
  7.  */  
  8. public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {  
  9.     Thread.defaultUncaughtHandler = handler;  
  10. }  

       这个方法可以设置系统的默认异常处理器,我们可以利用这个方法解决应用中常见的crash问题。当crash发生的时候,我们可以捕获到异常信息,把异常信息存储到SD卡中,然后在合适的时机通过网络将crash信息上传到服务器上,这样开发人员就可以分析用户crash的场景从而在后面的版本中修复此类crash。我们还可以在crash发生时,弹出一个通知告诉用户程序crash了,然后再退出,这样做比闪退要温和一点。

       2)再看一个Java中的接口——UncaughtExceptionHandler

static interface

  Thread.UncaughtExceptionHandler
           当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。


我们可以实现这个接口,然后在这个接口中的uncaughtException(Thread thread, Throwable ex)方法中实现对Crash信息的捕获。

3.思路描述

  1. /* 
  2.          * 1.捕获异常信息; 
  3.          * 2.上报手机和App的相关信息 
  4.          *   1).VersionCode 
  5.          *   2).VersionName 
  6.          *   3).PackageName 
  7.          *   4).手机分辨率 
  8.          *   5).手机系统版本号 
  9.          *   6).手机品牌 
  10.          *   7).手及型号 
  11.          *   8).CPU架构 
  12.          * 3.将以上信息写入指定的Crash日志文件中 
  13.          *   1)假如写入SDCard中,则首先判断SDCard是否可用 
  14.          *   2)假如可用,判断指定文件夹是否存在 
  15.          *   3)如果不存在则创建一个文件夹,如果存在继续 
  16.          *   4)常见一个以当前时间为文件名的日志文件并将Crash信息写入 
  17.          * 4.适当的时机将Crash日志信息上传到服务器 
  18.          *   1)Crash产生后,如果网络等条件允许则立即上传 
  19.          *   2)如果网络等条件有限,则软件下次启动时检测有无Crash信息 
  20.          *   3)如果有且网络正常,上传到服务器,否则重复(3) 
  21.          */  

4.具体实现

1)实现异常处理CrashHandler

  1. package com.example.crash.overall;  
  2.   
  3. import java.io.BufferedWriter;  
  4. import java.io.File;  
  5. import java.io.FileWriter;  
  6. import java.io.IOException;  
  7. import java.io.PrintWriter;  
  8. import java.lang.Thread.UncaughtExceptionHandler;  
  9. import java.text.SimpleDateFormat;  
  10. import java.util.Date;  
  11.   
  12. import com.example.crash.utils.AppUtil;  
  13.   
  14. import android.content.Context;  
  15. import android.os.Environment;  
  16. import android.os.Process;  
  17. import android.util.Log;  
  18.   
  19. /** 
  20.  * Description: Crash异常信息搜集类 
  21.  *  
  22.  * @author danDingCongRong 
  23.  * @Version 1.0.0 
  24.  * @Created at 2014-7-28 14:06:49 
  25.  * @Modified by [作者] on [修改日期] 
  26.  */  
  27. public class CrashHandler implements UncaughtExceptionHandler {  
  28.   
  29.     private static final String TAG = "CrashHandler";  
  30.   
  31.     private Context context;  
  32.   
  33.     private UncaughtExceptionHandler defaultExceptionHandler;  
  34.   
  35.     private static CrashHandler crashHandler;  
  36.   
  37.     private CrashHandler() {  
  38.         ;  
  39.     }  
  40.   
  41.     // 单例模式  
  42.     public static CrashHandler getInstance() {  
  43.         if (null == crashHandler) {  
  44.             crashHandler = new CrashHandler();  
  45.         }  
  46.   
  47.         return crashHandler;  
  48.     }  
  49.   
  50.     public void init(Context context) {  
  51.         // 系统默认的异常处理器  
  52.         defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();  
  53.         // 将当前实例设为系统默认的异常处理器  
  54.         Thread.setDefaultUncaughtExceptionHandler(this);  
  55.         // 获取App信息时用户此参数  
  56.         context = context.getApplicationContext();  
  57.     }  
  58.   
  59.     @Override  
  60.     public void uncaughtException(Thread thread, Throwable ex) {  
  61.   
  62.         // 导出崩溃信息到日志文件  
  63.         dumpExceptionToSDCard(ex);  
  64.   
  65.         // 打印崩溃信息到Log日志  
  66.         ex.printStackTrace();  
  67.   
  68.         // 如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己  
  69.         if (null != defaultExceptionHandler) {  
  70.             defaultExceptionHandler.uncaughtException(thread, ex);  
  71.         } else {  
  72.             Process.killProcess(Process.myPid());  
  73.         }  
  74.     }  
  75.   
  76.     // 导出崩溃信息到日志文件  
  77.     private void dumpExceptionToSDCard(Throwable ex) {  
  78.         // 检测SDCard是否可用--如果不可以在log中给予提醒  
  79.         if (!Environment.getExternalStorageState().equals(  
  80.                 Environment.MEDIA_MOUNTED)) {  
  81.             Log.w(TAG, "SDCard unmounted!");  
  82.             return;  
  83.         }  
  84.   
  85.         // 判断Crash文件夹是否存在,如果不存在则创建  
  86.         File crashDir = new File(Constants.CRASH_FILE_PATH);  
  87.         if (!crashDir.exists()) {  
  88.             crashDir.mkdir();  
  89.         }  
  90.   
  91.         // 创建当前崩溃日志文件  
  92.         String currentTime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")  
  93.                 .format(new Date(System.currentTimeMillis()));  
  94.         String logFileName = currentTime + ".txt";  
  95.         File logFile = new File(crashDir, logFileName);  
  96.   
  97.         BufferedWriter bufferedWriter = null;  
  98.         PrintWriter printWriter = null;  
  99.         try {  
  100.             bufferedWriter = new BufferedWriter(new FileWriter(logFile));  
  101.             printWriter = new PrintWriter(bufferedWriter);  
  102.   
  103.             // 崩溃发生时间  
  104.             printWriter.write(currentTime);  
  105.             // 崩溃手机的系统信息及其用户所用的软件信息  
  106.             String phoneInfo = new AppUtil(context).getPhoneInfoToCrash();  
  107.             printWriter.write(phoneInfo);  
  108.             // 崩溃信息  
  109.             ex.printStackTrace(printWriter);  
  110.   
  111.             printWriter.flush();  
  112.         } catch (IOException e) {  
  113.             e.printStackTrace();  
  114.         } finally {  
  115.             if (null != bufferedWriter) {  
  116.                 try {  
  117.                     bufferedWriter.close();  
  118.                 } catch (IOException e) {  
  119.                     e.printStackTrace();  
  120.                 }  
  121.             }  
  122.   
  123.             if (null != printWriter) {  
  124.                 printWriter.close();  
  125.             }  
  126.         }  
  127.     }  
  128.   
  129. }  

2)获取手机及App软件等相关信息

  1. package com.example.crash.utils;  
  2.   
  3. import android.content.Context;  
  4. import android.content.pm.PackageInfo;  
  5. import android.content.pm.PackageManager;  
  6. import android.content.pm.PackageManager.NameNotFoundException;  
  7.   
  8. /** 
  9.  * Description: 用户获取软件信息和系统信息的工具 
  10.  *  
  11.  * @author danDingCongRong 
  12.  * @Version 1.0.0 
  13.  * @Created at 2014-7-28 15:05:41 
  14.  * @Modified by [作者] on [修改日期] 
  15.  */  
  16. public class AppUtil {  
  17.   
  18.     private Context context;  
  19.   
  20.     public AppUtil(Context context) {  
  21.         this.context = context;  
  22.     }  
  23.   
  24.     // 获取App的VersionCode  
  25.     public int getAppVersionCode() {  
  26.         PackageManager packageManager = context.getPackageManager();  
  27.   
  28.         PackageInfo packageInfo = null;  
  29.         try {  
  30.             packageInfo = packageManager.getPackageInfo(  
  31.                     context.getPackageName(), PackageManager.GET_ACTIVITIES);  
  32.         } catch (NameNotFoundException e) {  
  33.             e.printStackTrace();  
  34.         }  
  35.   
  36.         int versionCode = 0;  
  37.         if (null != packageInfo) {  
  38.             versionCode = packageInfo.versionCode;  
  39.         }  
  40.   
  41.         return versionCode;  
  42.     }  
  43.   
  44.     // 获取App的VersionName  
  45.     public String getAppVersionName() {  
  46.         PackageManager packageManager = context.getPackageManager();  
  47.   
  48.         PackageInfo packageInfo = null;  
  49.         try {  
  50.             packageInfo = packageManager.getPackageInfo(  
  51.                     context.getPackageName(), PackageManager.GET_ACTIVITIES);  
  52.         } catch (NameNotFoundException e) {  
  53.             e.printStackTrace();  
  54.         }  
  55.   
  56.         return packageInfo.versionName;  
  57.     }  
  58.   
  59.     public String getPhoneInfoToCrash() {  
  60.         StringBuilder stringBuilder = new StringBuilder();  
  61.   
  62.         stringBuilder.append("PackageName:").append(context.getPackageName())  
  63.                 .append('\n');  
  64.         stringBuilder.append("VesionCode:").append(getAppVersionCode())  
  65.                 .append('\n');  
  66.         stringBuilder.append("VersionName:").append(getAppVersionName())  
  67.                 .append('\n');  
  68.         stringBuilder.append("OS Version:")  
  69.                 .append(android.os.Build.VERSION.RELEASE).append('_')  
  70.                 .append(android.os.Build.VERSION.SDK_INT).append('\n');  
  71.         stringBuilder.append("Model:").append(android.os.Build.MODEL)  
  72.                 .append('\n');  
  73.         stringBuilder.append("Manufacturer:")  
  74.                 .append(android.os.Build.MANUFACTURER).append('\n');  
  75.         stringBuilder.append("CPU:").append(android.os.Build.CPU_ABI)  
  76.                 .append('\n');  
  77.   
  78.         return stringBuilder.toString();  
  79.     }  
  80. }  

3)为UI线程添加默认异常事件Handler

//Thread类中标识默认异常事件Handler的成员

private static UncaughtExceptionHandler defaultUncaughtHandler;

这里涉及到在哪里添加的问题,从源码中注意到,这个defaultUncaughtHandler是Thread类中一个静态的成员,所以,按道理,我们为任意一个线程设置异常处理,所有的线程都应该能共用这个异常处理器,这个是我的猜测,没有经过验证,不过没关系,有一个观点是大家都认可的:就是为主线程也就是ui线程添加异常程序器。为了在ui线程中添加异常处理Handler,我们推荐大家在Application中添加而不是在Activity中添加。Application标识着整个应用,在Android声明周期中是第一个启动的,早于任何的Activity、Service等。

  1. /** 
  2.  *  
  3.  */  
  4. package com.example.crash.overall;  
  5.   
  6. import android.app.Application;  
  7.   
  8. /** 
  9.  * Description: Application 
  10.  *  
  11.  * @author danDingCongRong 
  12.  * @Version 1.0.0 
  13.  * @Created at 2014-7-28 14:04:06 
  14.  * @Modified by [作者] on [修改日期] 
  15.  */  
  16. public class App extends Application {  
  17.   
  18.     @Override  
  19.     public void onCreate() {  
  20.         super.onCreate();  
  21.   
  22.         // 开启崩溃日志,这样我们的程序就能捕获未处理的异常  
  23.         CrashHandler crashHandler = CrashHandler.getInstance();  
  24.         crashHandler.init(getApplicationContext());  
  25.     }  
  26.   
  27. }  

4.测试代码

  1. package com.example.crash.activity;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.Menu;  
  6. import android.view.View;  
  7.   
  8. import com.example.crash.R;  
  9.   
  10. public class MainActivity extends Activity {  
  11.   
  12.     @Override  
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.activity_main);  
  16.   
  17.     }  
  18.   
  19.     @Override  
  20.     public boolean onCreateOptionsMenu(Menu menu) {  
  21.         // Inflate the menu; this adds items to the action bar if it is present.  
  22.         getMenuInflater().inflate(R.menu.main, menu);  
  23.         return true;  
  24.     }  
  25.   
  26.     public void testCrash(View view) {  
  27.         if (1 > 0) {  
  28.             throw new RuntimeException("Test Exception!!!");  
  29.         }  
  30.     }  
  31.   
  32. }  

5.备注

     日志的上传属于网络相关的内容,暂且不做专门介绍




参考:

       1.http://blog.csdn.net/singwhatiwanna/article/details/17289479

       2.http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html

       3.http://blog.csdn.net/wangjia55/article/details/14000833

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值