android 程序崩溃信息的收集【使用CrashHandler来收集应用的crash信息】

1.什么是crash

android应用程序不可避免的会发生crash,也称之为崩溃,表现形式为闪退或是程序已停止运行。

2.发生crash的原因

发生crash的原因有很多:

  • 程序本身bug

  • android系统底层bug

  • 机型不适配或是网络状况糟糕

3.使用CrashHandler来获取应用的crash,保存于本地或是上传于服务器

  1. crash代码块
package com.example.demo3;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

public class CrashHandler implements UncaughtExceptionHandler {
  public static final String TAG = "CrashHandler";

  // 系统默认的UncaughtException处理类
  private Thread.UncaughtExceptionHandler mDefaultHandler;
  // CrashHandler实例
  private static CrashHandler INSTANCE = new CrashHandler();
  // 程序的Context对象
  private Context mContext;
  // 用来存储设备信息和异常信息
  private Map<String, String> infos = new HashMap<String, String>();
  // 用于格式化日期,作为日志文件名的一部分
  private DateFormat formatter = new SimpleDateFormat("yyyyMMdd:HHmmss");

  /** 保证只有一个CrashHandler实例 */
  private CrashHandler() {
  }

  /** 获取CrashHandler实例 ,单例模式 */
  public static CrashHandler getInstance() {
    return INSTANCE;
  }

  /**
   * 初始化
   * 
   * @param context
   */
  public void init(Context context) {
    mContext = context;
    // 获取系统默认的UncaughtException处理器
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    // 设置该CrashHandler为程序的默认处理器
    Thread.setDefaultUncaughtExceptionHandler(this);
  }

  /**
   * 当UncaughtException发生时会转入该函数来处理
   */
  @Override
  public void uncaughtException(Thread thread, Throwable ex) {

      if (!handleException(ex) && mDefaultHandler != null) {
      // 如果用户没有处理则让系统默认的异常处理器来处理
          mDefaultHandler.uncaughtException(thread, ex);
       } else {
      try {
        Thread.sleep(3000);
      } catch (InterruptedException e) {
        Log.e(TAG, "error : ", e);
      }
      // 退出程序
      android.os.Process.killProcess(android.os.Process.myPid());
      System.exit(1);
    }
  }

  /**
   * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
   * 
   * @param ex
   * @return true:如果处理了该异常信息;否则返回false.
   */
  private boolean handleException(Throwable ex) {
    if (ex == null) {
      return false;
    }
    // 使用Toast来显示异常信息
    new Thread() {
      @Override
      public void run() {
        Looper.prepare();
        Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG)
            .show();
        Looper.loop();
      }
    }.start();
    // 收集设备参数信息
    collectDeviceInfo(mContext);
    // 保存日志文件
    saveCrashInfo2File(ex,mContext);
    return true;
  }

  /**
   * 收集设备参数信息
   * 
   * @param ctx
   */
  public void collectDeviceInfo(Context ctx) {
    try {
      PackageManager pm = ctx.getPackageManager();
      PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
          PackageManager.GET_ACTIVITIES);
      if (pi != null) {
        String versionName = pi.versionName == null ? "null"
            : pi.versionName;
        String versionCode = pi.versionCode + "";
        infos.put("versionName", versionName);
        infos.put("versionCode", versionCode);
      }
    } catch (NameNotFoundException e) {
      Log.e(TAG, "an error occured when collect package info", e);
    }
    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
      try {
        field.setAccessible(true);
        infos.put(field.getName(), field.get(null).toString());
        Log.d(TAG, field.getName() + " : " + field.get(null));
      } catch (Exception e) {
        Log.e(TAG, "an error occured when collect crash info", e);
      }
    }
  }

  /**
   * 保存错误信息到文件中
   * 
   * @param ex
   * @return 返回文件名称,便于将文件传送到服务器
   */
  private String saveCrashInfo2File(Throwable ex,Context ctx) {

    StringBuffer sb = new StringBuffer();
    for (Map.Entry<String, String> entry : infos.entrySet()) {
      String key = entry.getKey();
      String value = entry.getValue();
      sb.append(key + "=" + value + "\n");
    }

    Writer writer = new StringWriter();
    PrintWriter printWriter = new PrintWriter(writer);
    ex.printStackTrace(printWriter);
    Throwable cause = ex.getCause();
    while (cause != null) {
      cause.printStackTrace(printWriter);
      cause = cause.getCause();
    }
    printWriter.close();
    String result = writer.toString();
    sb.append(result);
    try {
      long timestamp = System.currentTimeMillis();
      String time = formatter.format(new Date());
      String fileName = "crash-" + ctx.getPackageName()+"-"+time +"-" + timestamp + ".log";
      if (Environment.getExternalStorageState().equals(
          Environment.MEDIA_MOUNTED)) {
        String path = "/sdcard/crash/";
        File dir = new File(path);
        if (!dir.exists()) {
          dir.mkdirs();
        }
        FileOutputStream fos = new FileOutputStream(path + fileName);
        fos.write(sb.toString().getBytes());
        fos.close();
      }
      return fileName;
    } catch (Exception e) {
      Log.e(TAG, "an error occured while writing file...", e);
    }
    return null;
  }
}

2.在Application中使用

package com.example.demo3;

import android.app.Application;

public class TestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext());
    }

}

3.测试代码

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int i = 5/0;
        System.out.println(i);
    }
}

4.manifest中的权限

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

5.查看crash内容

IME=1440991413000
FINGERPRINT=rk30sdk/rk30sdk/rk30sdk:4.2.2/JDQ39/shuliwu.20150831.112131:eng/test-keys
HARDWARE=rk30board
UNKNOWN=unknown
RADIO=unknown
BOARD=rk30sdk
versionCode=1
PRODUCT=rk30sdk
versionName=1.0
USE_LCDC_COMPOSER=false
DISPLAY=D92_0001.06V1.0.1.SM01.20150831.05
USER=shuliwu
HOST=hwapu-254
DEVICE=rk30sdk
TAGS=test-keys
MODEL=KORIDY D92
BOOTLOADER=unknown
CPU_ABI=armeabi-v7a
CPU_ABI2=armeabi
IS_DEBUGGABLE=true
ID=JDQ39
SERIAL=4U8RUVPZ2L
MANUFACTURER=KORIDY
BRAND=rk30sdk
TYPE=eng
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.demo3/com.example.demo3.MainActivity}: java.lang.ArithmeticException: divide by zero
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
at android.app.ActivityThread.access 600(ActivityThread.java:141)atandroid.app.ActivityThread H.handleMessage(ActivityThread.java:1234)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit MethodAndArgsCaller.run(ZygoteInit.java:793)atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)atdalvik.system.NativeStart.main(NativeMethod)Causedby:java.lang.ArithmeticException:dividebyzeroatcom.example.demo3.MainActivity.onCreate(MainActivity.java:20)atandroid.app.Activity.performCreate(Activity.java:5104)atandroid.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)atandroid.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)11morejava.lang.ArithmeticException:dividebyzeroatcom.example.demo3.MainActivity.onCreate(MainActivity.java:20)atandroid.app.Activity.performCreate(Activity.java:5104)atandroid.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)atandroid.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)atandroid.app.ActivityThread.access 600(ActivityThread.java:141)
at android.app.ActivityThread H.handleMessage(ActivityThread.java:1234)atandroid.os.Handler.dispatchMessage(Handler.java:99)atandroid.os.Looper.loop(Looper.java:137)atandroid.app.ActivityThread.main(ActivityThread.java:5041)atjava.lang.reflect.Method.invokeNative(NativeMethod)atjava.lang.reflect.Method.invoke(Method.java:511)atcom.android.internal.os.ZygoteInit MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
包括设备相关信息和异常信息

4.使用CrashHelper架包来记录crash

  1. 下载CrashHelper架包
  2. 在Application中注入:
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
//将crash信息上传到服务器    
crashHandler.setOnCrashReportService(new OnCrashReportService() {

            @Override
            public void postService(String arg0, String arg1) {
                //文件上传操作 服务器一定要配置
                try {
                    File file = new File(arg0);
                    if (isNetworkConnected() && file.exists()) {
                        String fileName = file.getName();
                        String fileNameNew = fileName.substring(0, fileName.lastIndexOf("."));
                        HttpUtils httpUtils = new HttpUtils();
                        RequestParams params = new RequestParams();
                        params.addQueryStringParameter("obj", "{'fileType':'log','fileSource':'" + "logcat/"
                                + "','fileLimitSize':'20M','fileName':'" + fileNameNew + "'}");
                        params.addBodyParameter("files.files", new File(arg0));
                        httpUtils.sendSync(HttpMethod.POST, serverInfo.getIpAddressAndPort() + "/saveLogFileJson.do",
                                params);
                    }
                } catch (HttpException e) {
                    e.printStackTrace();
                }
            }
        });
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值