zoukankan      html  css  js  c++  java
  • Android反馈Crash报告(转)

    先建立一个crash项目,项目结构如图:

    在MainActivity.java代码中,代码是这样写的:

    1. package com.scott.crash;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5.   
    6. public class MainActivity extends Activity {  
    7.   
    8.     private String s;  
    9.       
    10.     @Override  
    11.     public void onCreate(Bundle savedInstanceState) {  
    12.         super.onCreate(savedInstanceState);  
    13.         System.out.println(s.equals("any string"));  
    14.     }  
    15. }  

     我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:

    遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。

    我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的bug的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。

    接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。

    Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。

    Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。

    大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:

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

    在收集异常信息时,朋友们也可以使用Properties,因为Properties有一个很便捷的方法properties.store(OutputStream out, String comments),用来将Properties实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成Map来存放这些信息,然后生成文件时稍加了些操作。

    完成这个CrashHandler后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,CrashApplication.java代码如下:

    1. package com.scott.crash;  
    2.   
    3. import android.app.Application;  
    4.   
    5. public class CrashApplication extends Application {  
    6.     @Override  
    7.     public void onCreate() {  
    8.         super.onCreate();  
    9.         CrashHandler crashHandler = CrashHandler.getInstance();  
    10.         crashHandler.init(getApplicationContext());  
    11.     }  
    12. }  

    最后,为了让我们的CrashApplication取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:

    1. <application android:name=".CrashApplication" ...>  
    2. </application>  

    因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:

    1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

    搞定了上边的步骤之后,我们来运行一下这个项目:

    看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。

    然后看一下SDCARD生成的文件:



    用文本编辑器打开日志文件,看一段日志信息:

    1. CPU_ABI=armeabi  
    2. CPU_ABI2=unknown  
    3. ID=FRF91  
    4. MANUFACTURER=unknown  
    5. BRAND=generic  
    6. TYPE=eng  
    7. ......  
    8. Caused by: java.lang.NullPointerException  
    9.     at com.scott.crash.MainActivity.onCreate(MainActivity.java:13)  
    10.     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
    11.     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)  
    12.     ... 11 more  

    这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照Android中使用HTTP服务相关介绍。

    不过在使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:

      1. /** 
      2.      * 网络是否可用 
      3.      *  
      4.      * @param context 
      5.      * @return 
      6.      */  
      7.     public static boolean isNetworkAvailable(Context context) {  
      8.         ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);  
      9.         NetworkInfo[] info = mgr.getAllNetworkInfo();  
      10.         if (info != null) {  
      11.             for (int i = 0; i < info.length; i++) {  
      12.                 if (info[i].getState() == NetworkInfo.State.CONNECTED) {  
      13.                     return true;  
      14.                 }  
      15.             }  
      16.         }  
      17.         return false;  
      18.     }  
  • 相关阅读:
    C++ list<list<int> >类型的对象遍历
    Apache与Nginx服务器对比
    服务器重写技术:rewrite
    冒泡排序(python版)
    有k个list列表, 各个list列表的元素是有序的,将这k个列表元素进行排序( 基于堆排序的K路归并排序)
    堆排序(C++版)
    [转载] 单链表的相关操作
    TCP三次握手连接与四次握手断开
    [转载] TCP与UDP对比
    进程与线程的联系与区别
  • 原文地址:https://www.cnblogs.com/weixiao870428/p/3512564.html
Copyright © 2011-2022 走看看