[开源中国]android客户端-异常捕获

开源中国的异常捕获的处理类在AppException.java中,产生的堆栈信息不是太详细,尤其是没有到底是在哪行出错的。这样对于查看修改bug还是增加了工作量,这里对异常的堆栈信息修改了一下,经过实际测试,能够正确的显示是哪个类,哪行出的问题。

关键的代码是:

  public static StringBuffer getTraceInfo(Activity a, Throwable e) {
    StringBuffer sb = new StringBuffer();
    StackTraceElement[] stacks = e.getCause().getStackTrace();
    for (int i = 0; i < stacks.length; i++) {
      if (stacks[i].getClassName().contains(a.getLocalClassName())) {
        sb.append("class: ").append(stacks[i].getClassName()).append("; method: ")
            .append(stacks[i].getMethodName()).append("; line: ").append(stacks[i].getLineNumber())
            .append(";  Exception: ").append(e.getCause().toString());
        break;
      }
    }
    return sb;
  }

完整的类内容如下:

 

package com.childapp.exception;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;

import org.apache.commons.httpclient.HttpException;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
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.text.format.DateFormat;
import android.util.Log;
import android.widget.Toast;

import com.childapp.R;
import com.childapp.common.AppApplication;
import com.childapp.common.AppManager;
import com.childapp.common.GlobalConstants;
import com.childapp.utils.UIHelper;

/**
 * 应用程序异常类:用于捕获异常和提示错误信息
 *
 * @author liux (http://my.oschina.net/liux)
 * @version 1.0
 * @created 2012-3-21
 */
public class AppException extends Exception implements UncaughtExceptionHandler {

  /**
   *
   */
  private static final long serialVersionUID = 6243307165131877535L;

  private final static boolean Debug = false;// 是否保存错误日志

  /** 定义异常类型 */
  public final static byte TYPE_NETWORK = 0x01;
  public final static byte TYPE_SOCKET = 0x02;
  public final static byte TYPE_HTTP_CODE = 0x03;
  public final static byte TYPE_HTTP_ERROR = 0x04;
  public final static byte TYPE_XML = 0x05;
  public final static byte TYPE_IO = 0x06;
  public final static byte TYPE_RUN = 0x07;

  private byte type;
  private int code;

  /** 系统默认的UncaughtException处理类 */
  private Thread.UncaughtExceptionHandler mDefaultHandler;

  private AppException() {
    this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
  }

  private AppException(byte type, int code, Exception excp) {
    super(excp);
    this.type = type;
    this.code = code;
    if (Debug) {
      this.saveErrorLog(excp);
    }
  }

  public int getCode() {
    return this.code;
  }

  public int getType() {
    return this.type;
  }

  /**
   * 提示友好的错误信息
   *
   * @param ctx
   */
  public void makeToast(Context ctx) {
    switch (this.getType()) {
      case TYPE_HTTP_CODE:
        String err = ctx.getString(R.string.http_status_code_error, this.getCode());
        Toast.makeText(ctx, err, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_HTTP_ERROR:
        Toast.makeText(ctx, R.string.http_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_SOCKET:
        Toast.makeText(ctx, R.string.socket_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_NETWORK:
        Toast.makeText(ctx, R.string.network_not_connected, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_XML:
        Toast.makeText(ctx, R.string.xml_parser_failed, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_IO:
        Toast.makeText(ctx, R.string.io_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_RUN:
        Toast.makeText(ctx, R.string.app_run_code_error, Toast.LENGTH_SHORT).show();
        break;
    }
  }

  /**
   * 保存异常日志
   *
   * @param excp
   */
  public void saveErrorLog(Exception excp) {
    saveErrorLog(excp.getLocalizedMessage());
  }

  /**
   * 保存异常日志
   *
   * @param excp
   */
  public void saveErrorLog(String excpMessage) {
    String errorlog = "errorlog.txt";
    String savePath = "";
    String logFilePath = "";
    FileWriter fw = null;
    PrintWriter pw = null;
    try {
      // 判断是否挂载了SD卡
      String storageState = Environment.getExternalStorageState();
      if (storageState.equals(Environment.MEDIA_MOUNTED)) {
        savePath =
            Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + GlobalConstants.FOLDER_ROOT + File.separator + "Log/";
        File file = new File(savePath);
        if (!file.exists()) {
          file.mkdirs();
        }
        logFilePath = savePath + errorlog;
      }
      // 没有挂载SD卡,无法写文件
      if (logFilePath == "") {
        return;
      }
      File logFile = new File(logFilePath);
      if (!logFile.exists()) {
        logFile.createNewFile();
      }
      fw = new FileWriter(logFile, true);
      pw = new PrintWriter(fw);
      pw.println("--------------------" + (DateFormat.format("yyyy-MM-dd hh:mm:ss", new Date()))
          + "---------------------");
      pw.println(excpMessage);
      pw.close();
      fw.close();
    } catch (Exception e) {
      Log.e("AppException", "[Exception]" + e.getLocalizedMessage());
    } finally {
      if (pw != null) {
        pw.close();
      }
      if (fw != null) {
        try {
          fw.close();
        } catch (IOException e) {}
      }
    }

  }

  public static AppException http(int code) {
    return new AppException(TYPE_HTTP_CODE, code, null);
  }

  public static AppException http(Exception e) {
    return new AppException(TYPE_HTTP_ERROR, 0, e);
  }

  public static AppException socket(Exception e) {
    return new AppException(TYPE_SOCKET, 0, e);
  }

  public static AppException io(Exception e) {
    if (e instanceof UnknownHostException || e instanceof ConnectException) {
      return new AppException(TYPE_NETWORK, 0, e);
    } else if (e instanceof IOException) {
      return new AppException(TYPE_IO, 0, e);
    }
    return run(e);
  }

  public static AppException xml(Exception e) {
    return new AppException(TYPE_XML, 0, e);
  }

  public static AppException network(Exception e) {
    if (e instanceof UnknownHostException || e instanceof ConnectException) {
      return new AppException(TYPE_NETWORK, 0, e);
    } else if (e instanceof HttpException) {
      return http(e);
    } else if (e instanceof SocketException) {
      return socket(e);
    }
    return http(e);
  }

  public static AppException run(Exception e) {
    return new AppException(TYPE_RUN, 0, e);
  }

  /**
   * 获取APP异常崩溃处理对象
   *
   * @param context
   * @return
   */
  public static AppException getAppExceptionHandler() {
    return new AppException();
  }

  @Override
  public void uncaughtException(Thread thread, Throwable ex) {

    if (!handleException(ex) && mDefaultHandler != null) {
      mDefaultHandler.uncaughtException(thread, ex);
    } else {
      try {
        Thread.sleep(15000);
      } catch (InterruptedException e) {
        Log.e("AppException", "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;
    }

    final Context context = AppManager.getAppManager().currentActivity();

    if (context == null) {
      return false;
    }

    final String crashReport = getCrashReport(context, ex);
    // 显示异常信息&发送报告
    new Thread() {
      public void run() {
        Looper.prepare();
        UIHelper.sendAppCrashReport(context, crashReport);
        Looper.loop();
      }

    }.start();

    saveErrorLog(crashReport);

    return true;
  }

  /**
   * 获取APP崩溃异常报告
   *
   * @param ex
   * @return
   */
  private String getCrashReport(Context context, Throwable ex) {
    PackageInfo pinfo = ((AppApplication) context.getApplicationContext()).getPackageInfo();
    StringBuffer exceptionStr = new StringBuffer();
    exceptionStr.append("Version: " + pinfo.versionName + "(" + pinfo.versionCode + ")\n");
    exceptionStr.append("Android: " + android.os.Build.VERSION.RELEASE + "("
        + android.os.Build.MODEL + ")\n");
    exceptionStr.append("System package Info:" + collectDeviceInfo(context) + "\n");
    exceptionStr.append("System os Info:" + getMobileInfo() + "\n");
    exceptionStr.append("Exception: " + ex.getMessage() + "\n");
    exceptionStr.append("Exception stack:" + getTraceInfo((Activity) context, ex) + "\n");
    StackTraceElement[] elements = ex.getStackTrace();
    for (int i = 0; i < elements.length; i++) {
      exceptionStr.append(elements[i].toString() + "\n");
    }
    return exceptionStr.toString();
  }

  /**
   * 收集设备参数信息
   *
   * @param ctx
   */
  public String collectDeviceInfo(Context ctx) {
    StringBuilder sb = new StringBuilder();
    JSONObject activePackageJson = new JSONObject();

    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 + "";

        activePackageJson.put("versionName", versionName);
        activePackageJson.put("versionCode", versionCode);
      }
    } catch (NameNotFoundException e) {
      Log.e("AppException", "an error occured when collect package info", e);
    } catch (JSONException e) {
      Log.e("AppException", "jsonException", e);
    }
    sb.append("[active Package]");
    sb.append(activePackageJson.toString());

    return sb.toString();
  }

  public static StringBuffer getTraceInfo(Activity a, Throwable e) {
    StringBuffer sb = new StringBuffer();
    StackTraceElement[] stacks = e.getCause().getStackTrace();
    for (int i = 0; i < stacks.length; i++) {
      if (stacks[i].getClassName().contains(a.getLocalClassName())) {
        sb.append("class: ").append(stacks[i].getClassName()).append("; method: ")
            .append(stacks[i].getMethodName()).append("; line: ").append(stacks[i].getLineNumber())
            .append(";  Exception: ").append(e.getCause().toString());
        break;
      }
    }
    return sb;
  }

  /**
   * 获取手机的硬件信息
   *
   * @return
   */
  public String getMobileInfo() {
    JSONObject osJson = new JSONObject();
    // 通过反射获取系统的硬件信息

    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
      try {
        field.setAccessible(true);
        osJson.put(field.getName(), field.get(null).toString());
        Log.d("AppException", field.getName() + " : " + field.get(null));
      } catch (Exception e) {
        Log.e("AppException", "an error occured when collect crash info", e);
      }
    }

    return osJson.toString();
  }


}

 

时隔多日,发现上面的还是有些问题,进行了再次的优化,堆栈信息打的更加好了。代码如下:

 

 

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;

import org.apache.commons.httpclient.HttpException;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
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.text.format.DateFormat;
import android.util.Log;
import android.widget.Toast;

import com.childapp.R;
import com.childapp.common.AppApplication;
import com.childapp.common.AppManager;
import com.childapp.common.GlobalConstants;
import com.childapp.utils.UIHelper;

/**
 * 应用程序异常类:用于捕获异常和提示错误信息
 *
 * @author liux (http://my.oschina.net/liux)
 * @version 1.0
 * @created 2012-3-21
 */
public class AppException extends Exception implements UncaughtExceptionHandler {

  /**
   *
   */
  private static final long serialVersionUID = 6243307165131877535L;

  private final static boolean Debug = true;// 是否保存错误日志

  /** 定义异常类型 */
  public final static byte TYPE_NETWORK = 0x01;
  public final static byte TYPE_SOCKET = 0x02;
  public final static byte TYPE_HTTP_CODE = 0x03;
  public final static byte TYPE_HTTP_ERROR = 0x04;
  public final static byte TYPE_XML = 0x05;
  public final static byte TYPE_IO = 0x06;
  public final static byte TYPE_RUN = 0x07;

  private byte type;
  private int code;

  /** 系统默认的UncaughtException处理类 */
  private Thread.UncaughtExceptionHandler mDefaultHandler;

  private AppException() {
    this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
  }

  private AppException(byte type, int code, Exception excp) {
    super(excp);
    this.type = type;
    this.code = code;
    if (Debug) {
      this.saveErrorLog(excp);
    }
  }

  public int getCode() {
    return this.code;
  }

  public int getType() {
    return this.type;
  }

  /**
   * 提示友好的错误信息
   *
   * @param ctx
   */
  public void makeToast(Context ctx) {
    switch (this.getType()) {
      case TYPE_HTTP_CODE:
        String err = ctx.getString(R.string.http_status_code_error, this.getCode());
        Toast.makeText(ctx, err, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_HTTP_ERROR:
        Toast.makeText(ctx, R.string.http_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_SOCKET:
        Toast.makeText(ctx, R.string.socket_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_NETWORK:
        Toast.makeText(ctx, R.string.network_not_connected, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_XML:
        Toast.makeText(ctx, R.string.xml_parser_failed, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_IO:
        Toast.makeText(ctx, R.string.io_exception_error, Toast.LENGTH_SHORT).show();
        break;
      case TYPE_RUN:
        Toast.makeText(ctx, R.string.app_run_code_error, Toast.LENGTH_SHORT).show();
        break;
    }
  }

  /**
   * 保存异常日志
   *
   * @param excp
   */
  public void saveErrorLog(Exception excp) {
    saveErrorLog(excp.getLocalizedMessage());
  }

  /**
   * 保存异常日志
   *
   * @param excp
   */
  public void saveErrorLog(String excpMessage) {
    String errorlog = "errorlog.txt";
    String savePath = "";
    String logFilePath = "";
    FileWriter fw = null;
    PrintWriter pw = null;
    try {
      // 判断是否挂载了SD卡
      String storageState = Environment.getExternalStorageState();
      if (storageState.equals(Environment.MEDIA_MOUNTED)) {
        savePath =
            Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + GlobalConstants.FOLDER_ROOT + File.separator + "Log/";
        File file = new File(savePath);
        if (!file.exists()) {
          file.mkdirs();
        }
        logFilePath = savePath + errorlog;
      }
      // 没有挂载SD卡,无法写文件
      if (logFilePath == "") {
        return;
      }
      File logFile = new File(logFilePath);
      if (!logFile.exists()) {
        logFile.createNewFile();
      }
      fw = new FileWriter(logFile, true);
      pw = new PrintWriter(fw);
      pw.println("--------------------" + (DateFormat.format("yyyy-MM-dd hh:mm:ss", new Date()))
          + "---------------------");
      pw.println(excpMessage);
      pw.close();
      fw.close();
    } catch (Exception e) {
      Log.e("AppException", "[Exception]" + e.getLocalizedMessage());
    } finally {
      if (pw != null) {
        pw.close();
      }
      if (fw != null) {
        try {
          fw.close();
        } catch (IOException e) {}
      }
    }

  }

  public static AppException http(int code) {
    return new AppException(TYPE_HTTP_CODE, code, null);
  }

  public static AppException http(Exception e) {
    return new AppException(TYPE_HTTP_ERROR, 0, e);
  }

  public static AppException socket(Exception e) {
    return new AppException(TYPE_SOCKET, 0, e);
  }

  public static AppException io(Exception e) {
    if (e instanceof UnknownHostException || e instanceof ConnectException) {
      return new AppException(TYPE_NETWORK, 0, e);
    } else if (e instanceof IOException) {
      return new AppException(TYPE_IO, 0, e);
    }
    return run(e);
  }

  public static AppException xml(Exception e) {
    return new AppException(TYPE_XML, 0, e);
  }

  public static AppException network(Exception e) {
    if (e instanceof UnknownHostException || e instanceof ConnectException) {
      return new AppException(TYPE_NETWORK, 0, e);
    } else if (e instanceof HttpException) {
      return http(e);
    } else if (e instanceof SocketException) {
      return socket(e);
    }
    return http(e);
  }

  public static AppException run(Exception e) {
    return new AppException(TYPE_RUN, 0, e);
  }

  /**
   * 获取APP异常崩溃处理对象
   *
   * @param context
   * @return
   */
  public static AppException getAppExceptionHandler() {
    return new AppException();
  }

  @Override
  public void uncaughtException(Thread thread, Throwable ex) {

    if (!handleException(ex) && mDefaultHandler != null) {
      mDefaultHandler.uncaughtException(thread, ex);
    } else {
      try {
        Thread.sleep(15000);
      } catch (InterruptedException e) {
        Log.e("AppException", "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;
    }

    final Context context = AppManager.getAppManager().currentActivity();

    if (context == null) {
      return false;
    }

    final String crashReport = getCrashReport(context, ex);
    // 显示异常信息&发送报告
    new Thread() {
      public void run() {
        Looper.prepare();
        UIHelper.sendAppCrashReport(context, crashReport);
        Looper.loop();
      }

    }.start();

    saveErrorLog(crashReport);

    return true;
  }

  /**
   * 获取APP崩溃异常报告
   *
   * @param ex
   * @return
   */
  private String getCrashReport(Context context, Throwable ex) {
    PackageInfo pinfo = ((AppApplication) context.getApplicationContext()).getPackageInfo();
    StringBuffer exceptionStr = new StringBuffer();
    exceptionStr.append("Version: " + pinfo.versionName + "(" + pinfo.versionCode + ")\n");
    exceptionStr.append("Android: " + android.os.Build.VERSION.RELEASE + "("
        + android.os.Build.MODEL + ")\n");
    exceptionStr.append("System package Info:" + collectDeviceInfo(context) + "\n");
    exceptionStr.append("System os Info:" + getMobileInfo() + "\n");
    exceptionStr.append("Exception: " + ex.getMessage() + "\n");
    exceptionStr.append("Exception stack:" + getTraceInfo((Activity) context, ex) + "\n");

    return exceptionStr.toString();
  }

  /**
   * 收集设备参数信息
   *
   * @param ctx
   */
  public String collectDeviceInfo(Context ctx) {
    StringBuilder sb = new StringBuilder();
    JSONObject activePackageJson = new JSONObject();

    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 + "";

        activePackageJson.put("versionName", versionName);
        activePackageJson.put("versionCode", versionCode);
      }
    } catch (NameNotFoundException e) {
      Log.e("AppException", "an error occured when collect package info", e);
    } catch (JSONException e) {
      Log.e("AppException", "jsonException", e);
    }
    sb.append("[active Package]");
    sb.append(activePackageJson.toString());

    return sb.toString();
  }

  public static StringBuffer getTraceInfo(Activity a, Throwable e) {
    StringBuffer sb = new StringBuffer();

    Throwable ex = e.getCause() == null ? e : e.getCause();
    StackTraceElement[] stacks = ex.getStackTrace();
    for (int i = 0; i < stacks.length; i++) {
      sb.append("class: ").append(stacks[i].getClassName()).append("; method: ")
          .append(stacks[i].getMethodName()).append("; line: ").append(stacks[i].getLineNumber())
          .append(";  Exception: ").append(ex.toString() + "\n");
    }
    return sb;
  }

  /**
   * 获取手机的硬件信息
   *
   * @return
   */
  public String getMobileInfo() {
    JSONObject osJson = new JSONObject();
    // 通过反射获取系统的硬件信息

    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
      try {
        field.setAccessible(true);
        osJson.put(field.getName(), field.get(null).toString());
        Log.d("AppException", field.getName() + " : " + field.get(null));
      } catch (Exception e) {
        Log.e("AppException", "an error occured when collect crash info", e);
      }
    }

    return osJson.toString();
  }


}

转载于:https://my.oschina.net/u/565459/blog/145726

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值