今天,我来回顾一下如何友好的处理程序异常。
目前,我学习两种方法去实现友好界面处理异常
方法一: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;
}
//
// 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;
}
}
同时,我们也可以自定义方法去实现实现显示的方式。