学习和使用程序捕捉异常 CrashHandler以及ACRA

今天,我来回顾一下如何友好的处理程序异常。

目前,我学习两种方法去实现友好界面处理异常

方法一:CrashHandler(老的使用办法)

方法二:ACRA(Google推荐)

1.CrashHandler如何使用?

1.1CrashHandler类

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
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.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import android.annotation.SuppressLint;
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.util.Log;

@SuppressLint("SimpleDateFormat")
public class CrashHandler implements UncaughtExceptionHandler {

	public static final String TAG = "CrashHandler";
	List<String>  filepath=new ArrayList<String>();
	// 系统默认的UncaughtException处理类
	private UncaughtExceptionHandler mDefaultHandler;
	// CrashHandler实例
	private static CrashHandler INSTANCE = new CrashHandler();
	// 程序的Context对象
	private Context mContext;
	// 用来存储设备信息和异常信息
	private Map<String, String> infos = new HashMap<String, String>();
	CrashLogUpload crashLogUpload = new CrashLogUpload();
	// 用于格式化日期,作为日志文件名的一部分
	private DateFormat formatter = new SimpleDateFormat("MMddHHmm");
	
	public static List<CrashLogUpload>  crashLogUploadList=new ArrayList<CrashLogUpload>(); ;
	
	private List<Activity> activityList = new LinkedList<Activity>();

	/** 保证只有一个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方法
     * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
     */
	@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);
			}
			exit();
		}
	}

	/**
	 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
	 * 
	 * @param ex
	 * @return true:如果处理了该异常信息;否则返回false.
	 */
	private boolean handleException(Throwable ex) {
		if (ex == null) {
			return false;
		}

		// 收集设备参数信息
		collectDeviceInfo(mContext);
		// 保存日志文件
		saveCrashInfo2File(ex);
		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) {

		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 {
		
			String time = formatter.format(new Date());
			String fileName = "crash" + time +".log";
			if (Environment.getExternalStorageState().equals(
					Environment.MEDIA_MOUNTED)) {
				String path = getOutputLoggerFileFloder();
				File dir = new File(path);
				if (!dir.exists()) {
					dir.mkdirs();
				}
				File file = new File(path + File.separator + fileName);
				FileWriter filerWriter = new FileWriter(file, true);// 后面这个参数代表是不是要接上文件中原来的数据,不进行覆盖
				BufferedWriter bufWriter = new BufferedWriter(filerWriter);
				bufWriter.write(sb.toString());
				bufWriter.newLine();
				bufWriter.close();
				filerWriter.close();
			}
		} catch (Exception e) {
			Log.e(TAG, "an error occured while writing file...", e);
		}
		return null;
	}

	private String getOutputLoggerFileFloder() {
		File directory = new File(Environment
				.getExternalStorageDirectory().getAbsolutePath()
				+ File.separator + "app_log" + File.separator + "Loggers");
		if (!directory.exists()) {
			directory.mkdirs();
		}
		return directory.getPath();
	}

	public void addActivity(Activity activity) {
		activityList.add(activity);
	}

	// 遍历所有Activity并finish
	public void exit() {
		for (Activity activity : activityList) {
			activity.finish();
		}
		android.os.Process.killProcess(android.os.Process.myPid());
		System.exit(1);
	}
}

1.2打印日志类

import java.util.Date;

public class CrashLogUpload {
	
	private String id;

	private String fileName;
	
	private String filePath;
	
	private Date recordTime;
	
	private Boolean hasUpload;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getFilePath() {
		return filePath;
	}

	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

	public Date getRecordTime() {
		return recordTime;
	}

	public void setRecordTime(Date recordTime) {
		this.recordTime = recordTime;
	}

	public Boolean getHasUpload() {
		return hasUpload;
	}

	public void setHasUpload(Boolean hasUpload) {
		this.hasUpload = hasUpload;
	} 
}

1.3在appliaction的oncrate()方法中初始化

        //在这里为应用设置异常处理程序,然后我们的程序才能捕获未处理的异常
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext());

2.如何使用Acra捕捉异常

2.1下载Acra架包,as library

下载地址:http://pan.baidu.com/s/1kVydkjl,然后考入lib中,右击as library

2.1 application中初始化

  ACRA.init(this);
可根据自身需求设置

有弹出框提示,有保存本地提示,有上传信息到服务器,有吐司等

最基本的

 @ReportsCrashes(
        formKey = "", // This is required for backward compatibility but not used
        formUri = "http://www.backendofyourchoice.com/reportpath"
    )

我们可以根据源码中和自己的需求来运用

ReportsCrashes源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.acra.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.acra.ReportField;
import org.acra.ReportingInteractionMode;
import org.acra.sender.HttpSender.Method;
import org.acra.sender.HttpSender.Type;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface ReportsCrashes {
    String formKey();

    String formUri() default "";

    ReportingInteractionMode mode() default ReportingInteractionMode.SILENT;

    int resDialogCommentPrompt() default 0;

    int resDialogEmailPrompt() default 0;

    int resDialogIcon() default 17301543;

    int resDialogOkToast() default 0;

    int resDialogText() default 0;

    int resDialogTitle() default 0;

    int resNotifIcon() default 17301624;

    int resNotifText() default 0;

    int resNotifTickerText() default 0;

    int resNotifTitle() default 0;

    int resToastText() default 0;

    String sharedPreferencesName() default "";

    int sharedPreferencesMode() default 0;

    boolean includeDropBoxSystemTags() default false;

    String[] additionalDropBoxTags() default {};

    int dropboxCollectionMinutes() default 5;

    String[] logcatArguments() default {"-t", "100", "-v", "time"};

    String formUriBasicAuthLogin() default "ACRA-NULL-STRING";

    String formUriBasicAuthPassword() default "ACRA-NULL-STRING";

    ReportField[] customReportContent() default {};

    String mailTo() default "";

    boolean deleteUnapprovedReportsOnApplicationStart() default true;

    boolean deleteOldUnsentReportsOnApplicationStart() default true;

    int connectionTimeout() default 3000;

    int socketTimeout() default 5000;

    int maxNumberOfRequestRetries() default 3;

    boolean forceCloseDialogAfterToast() default false;

    String[] additionalSharedPreferences() default {};

    boolean logcatFilterByPid() default false;

    boolean sendReportsInDevMode() default true;

    String[] excludeMatchingSharedPreferencesKeys() default {};

    String[] excludeMatchingSettingsKeys() default {};

    String applicationLogFile() default "";

    int applicationLogFileLines() default 100;

    String googleFormUrlFormat() default "https://docs.google.com/spreadsheet/formResponse?formkey=%s&ifq";

    boolean disableSSLCertValidation() default false;

    Method httpMethod() default Method.POST;

    Type reportType() default Type.FORM;
}


ACRA源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.acra;

import android.app.Application;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
import org.acra.ACRAConfiguration;
import org.acra.ACRAConfigurationException;
import org.acra.ErrorReporter;
import org.acra.annotation.ReportsCrashes;
import org.acra.log.ACRALog;
import org.acra.log.AndroidLogDelegate;

public class ACRA {
    public static final boolean DEV_LOGGING = false;
    public static final String LOG_TAG = ACRA.class.getSimpleName();
    public static ACRALog log = new AndroidLogDelegate();
    public static final String PREF_DISABLE_ACRA = "acra.disable";
    public static final String PREF_ENABLE_ACRA = "acra.enable";
    public static final String PREF_ENABLE_SYSTEM_LOGS = "acra.syslog.enable";
    public static final String PREF_ENABLE_DEVICE_ID = "acra.deviceid.enable";
    public static final String PREF_USER_EMAIL_ADDRESS = "acra.user.email";
    public static final String PREF_ALWAYS_ACCEPT = "acra.alwaysaccept";
    public static final String PREF_LAST_VERSION_NR = "acra.lastVersionNr";
    private static Application mApplication;
    private static ReportsCrashes mReportsCrashes;
    private static ErrorReporter errorReporterSingleton;
    private static OnSharedPreferenceChangeListener mPrefListener;
    private static ACRAConfiguration configProxy;

    public ACRA() {
    }

    public static void <span style="color:#ff0000;">init</span>(Application app) {
        if(mApplication != null) {
            log.w(LOG_TAG, "ACRA#init called more than once. Won\'t do anything more.");
        } else {
            mApplication = app;
            mReportsCrashes = (ReportsCrashes)mApplication.getClass().getAnnotation(ReportsCrashes.class);
            if(mReportsCrashes == null) {
                log.e(LOG_TAG, "ACRA#init called but no ReportsCrashes annotation on Application " + mApplication.getPackageName());
            } else {
                SharedPreferences prefs = getACRASharedPreferences();

                try {
                    checkCrashResources();
                    log.d(LOG_TAG, "ACRA is enabled for " + mApplication.getPackageName() + ", intializing...");
                    boolean e = !shouldDisableACRA(prefs);
                    ErrorReporter errorReporter = new ErrorReporter(mApplication, prefs, e);
                    errorReporter.setDefaultReportSenders();
                    errorReporterSingleton = errorReporter;
                } catch (ACRAConfigurationException var4) {
                    log.w(LOG_TAG, "Error : ", var4);
                }

                mPrefListener = new OnSharedPreferenceChangeListener() {
                    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                        if("acra.disable".equals(key) || "acra.enable".equals(key)) {
                            boolean enableAcra = !ACRA.shouldDisableACRA(sharedPreferences);
                            ACRA.getErrorReporter().setEnabled(enableAcra);
                        }

                    }
                };
                prefs.registerOnSharedPreferenceChangeListener(mPrefListener);
            }
        }
    }

    public static ErrorReporter getErrorReporter() {
        if(errorReporterSingleton == null) {
            throw new IllegalStateException("Cannot access ErrorReporter before ACRA#init");
        } else {
            return errorReporterSingleton;
        }
    }

    private static boolean shouldDisableACRA(SharedPreferences prefs) {
        boolean disableAcra = false;

        try {
            boolean e = prefs.getBoolean("acra.enable", true);
            disableAcra = prefs.getBoolean("acra.disable", !e);
        } catch (Exception var3) {
            ;
        }

        return disableAcra;
    }

    static void checkCrashResources() throws ACRAConfigurationException {
        ACRAConfiguration conf = getConfig();
        switch(ACRA.SyntheticClass_1.$SwitchMap$org$acra$ReportingInteractionMode[conf.mode().ordinal()]) {
        case 1:
            if(conf.resToastText() == 0) {
                throw new ACRAConfigurationException("TOAST mode: you have to define the resToastText parameter in your application @ReportsCrashes() annotation.");
            }
            break;
        case 2:
            if(conf.resNotifTickerText() == 0 || conf.resNotifTitle() == 0 || conf.resNotifText() == 0 || conf.resDialogText() == 0) {
                throw new ACRAConfigurationException("NOTIFICATION mode: you have to define at least the resNotifTickerText, resNotifTitle, resNotifText, resDialogText parameters in your application @ReportsCrashes() annotation.");
            }
            break;
        case 3:
            if(conf.resDialogText() == 0) {
                throw new ACRAConfigurationException("DIALOG mode: you have to define at least the resDialogText parameters in your application @ReportsCrashes() annotation.");
            }
        }

    }

    public static SharedPreferences getACRASharedPreferences() {
        ACRAConfiguration conf = getConfig();
        return !"".equals(conf.sharedPreferencesName())?mApplication.getSharedPreferences(conf.sharedPreferencesName(), conf.sharedPreferencesMode()):PreferenceManager.getDefaultSharedPreferences(mApplication);
    }

    public static ACRAConfiguration getConfig() {
        if(configProxy == null) {
            if(mApplication == null) {
                log.w(LOG_TAG, "Calling ACRA.getConfig() before ACRA.init() gives you an empty configuration instance. You might prefer calling ACRA.getNewDefaultConfig(Application) to get an instance with default values taken from a @ReportsCrashes annotation.");
            }

            configProxy = getNewDefaultConfig(mApplication);
        }

        return configProxy;
    }

    public static void setConfig(ACRAConfiguration conf) {
        configProxy = conf;
    }

    public static ACRAConfiguration getNewDefaultConfig(Application app) {
        return app != null?new ACRAConfiguration((ReportsCrashes)app.getClass().getAnnotation(ReportsCrashes.class)):new ACRAConfiguration((ReportsCrashes)null);
    }

    static boolean isDebuggable() {
        PackageManager pm = mApplication.getPackageManager();

        try {
            return (pm.getApplicationInfo(mApplication.getPackageName(), 0).flags & 2) > 0;
        } catch (NameNotFoundException var2) {
            return false;
        }
    }

    static Application getApplication() {
        return mApplication;
    }

    public static void setLog(ACRALog log) {
        log = log;
    }
}

同时,我们也可以自定义方法去实现实现显示的方式。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C++ 中,我们可以使用一些调试工具来获取程序崩溃后的调用栈信息。其中比较常用的是使用 GDB 或 Visual Studio 来进行调试。 在 GDB 中,我们可以使用 `bt` 命令来获取当前调用栈信息。当程序崩溃后,我们可以在 GDB 中输入 `bt` 命令来获取崩溃时的调用栈信息。 在 Visual Studio 中,我们可以使用调试功能来获取程序崩溃时的调用栈信息。在程序崩溃后,我们可以在 Visual Studio 中打开“调试器 -> Windows -> 调用栈”窗口来查看当前的调用栈信息。 如果我们想在程序崩溃后自动输出调用栈信息,可以使用一些第三方库,比如 Google 的 glog 库。我们可以在程序崩溃时使用 glog 库中的 `google::InstallFailureSignalHandler()` 函数来安装一个信号处理程序,在程序崩溃时自动输出调用栈信息。例如: ```c++ #include <glog/logging.h> void CrashHandler(int sig) { LOG(ERROR) << "Crash detected. Signal: " << sig; google::FlushLogFiles(google::GLOG_ERROR); } int main() { google::InitGoogleLogging("MyProgram"); google::InstallFailureSignalHandler(); signal(SIGSEGV, CrashHandler); // ... } ``` 上面的代码中,我们在程序启动时安装了一个信号处理程序 `CrashHandler()`,在程序崩溃时会被自动调用。在 `CrashHandler()` 中,我们可以使用 glog 库中的 `LOG(ERROR)` 函数来输出错误信息,使用 `google::FlushLogFiles(google::GLOG_ERROR)` 函数来将错误信息输出到日志文件中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值