開發中最讓人頭疼的是應用突然爆炸,然后跳回到桌面。而且我們常常不知道這種狀況會何時出現,在應用調試階段還好,還可以通過調試工具的日志查看錯誤出現在哪里。但平時使用的時候給你鬧崩潰,那你就欲哭無淚了。
那么今天主要講一下如何去捕捉系統出現的Unchecked異常。何為Unchecked異常呢,換句話說就是指非受檢異常,它不能用try-catch來顯示捕捉。
我們先從Exception講起。Exception分為兩類:一種是CheckedException,一種是UncheckedException。這兩種Exception的區別主要是CheckedException需要用try...catch...顯示的捕獲,而UncheckedException不需要捕獲。通常UncheckedException又叫做RuntimeException?!秂ffective java》指出:對于可恢復的條件使用被檢查的異常(CheckedException),對于程序錯誤(言外之意不可恢復,大錯已經釀成)使用運行時異常(RuntimeException)。我們常見的RuntimeExcepiton有IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等。對于那些CheckedException就不勝枚舉了,我們在編寫程序過程中try...catch...捕捉的異常都是CheckedException。io包中的IOException及其子類,這些都是CheckedException。
一、使用UncaughtExceptionHandler來捕獲unchecked異常
UncaughtException處理類,當程序發生Uncaught異常的時候,由該類來接管程序,并記錄發送錯誤報告。
直接上代碼吧
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.Locale;import java.util.Map;import java.util.Map.Entry;import java.util.regex.Matcher;import java.util.regex.Pattern;import android.annotation.SuppressLint;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;/*** UncaughtException處理類,當程序發生Uncaught異常的時候,有該類來接管程序,并記錄發送錯誤報告.* * @author user* */@SuppressLint("SdCardPath")public class CrashHandler implements UncaughtExceptionHandler {public static final String TAG = "TEST";// CrashHandler 實例private static CrashHandler INSTANCE = new CrashHandler();// 程序的 Context 對象private Context mContext;// 系統默認的 UncaughtException 處理類private Thread.UncaughtExceptionHandler mDefaultHandler;// 用來存儲設備信息和異常信息private Map<String, String> infos = new HashMap<String, String>();// 用來顯示Toast中的信息private static String error = "程序錯誤,額,不對,我應該說,服務器正在維護中,請稍后再試";private static final Map<String, String> regexMap = new HashMap<String, String>();// 用于格式化日期,作為日志文件名的一部分private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss",Locale.CHINA);/** 保證只有一個 CrashHandler 實例 */private CrashHandler() {//}/** 獲取 CrashHandler 實例 ,單例模式 */public static CrashHandler getInstance() {initMap();return INSTANCE;}/*** 初始化* * @param context*/public void init(Context context) {mContext = context;// 獲取系統默認的 UncaughtException 處理器mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 設置該 CrashHandler 為程序的默認處理器Thread.setDefaultUncaughtExceptionHandler(this);Log.d("TEST", "Crash:init");}/*** 當 UncaughtException 發生時會轉入該函數來處理*/@Overridepublic void uncaughtException(Thread thread, Throwable ex) {if (!handleException(ex) && mDefaultHandler != null) {// 如果用戶沒有處理則讓系統默認的異常處理器來處理mDefaultHandler.uncaughtException(thread, ex);Log.d("TEST", "defalut");} else {try {Thread.sleep();} catch (InterruptedException e) {Log.e(TAG, "error : ", e);}// 退出程序android.os.Process.killProcess(android.os.Process.myPid());// mDefaultHandler.uncaughtException(thread, ex);System.exit();}}/*** 自定義錯誤處理,收集錯誤信息,發送錯誤報告等操作均在此完成* * @param ex* @return true:如果處理了該異常信息;否則返回 false*/private boolean handleException(Throwable ex) {if (ex == null) {return false;}// 收集設備參數信息// collectDeviceInfo(mContext);// 保存日志文件saveCrashInfoFile(ex);// 使用 Toast 來顯示異常信息new Thread() {@Overridepublic void run() {Looper.prepare();Toast.makeText(mContext, error, Toast.LENGTH_LONG).show();Looper.loop();}}.start();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 saveCrashInfoFile(Throwable ex) {StringBuffer sb = getTraceInfo(ex);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-" + time + "-" + timestamp + ".log";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String path = Environment.getExternalStorageDirectory()+ "/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;}/*** 整理異常信息* @param e* @return*/public static StringBuffer getTraceInfo(Throwable e) {StringBuffer sb = new StringBuffer();Throwable ex = e.getCause() == null ? e : e.getCause();StackTraceElement[] stacks = ex.getStackTrace();for (int i = ; i < stacks.length; i++) {if (i == ) {setError(ex.toString());}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");}Log.d(TAG, sb.toString());return sb;}/*** 設置錯誤的提示語* @param e*/public static void setError(String e) {Pattern pattern;Matcher matcher;for (Entry<String, String> m : regexMap.entrySet()) {Log.d(TAG, e+"key:" + m.getKey() + "; value:" + m.getValue());pattern = Pattern.compile(m.getKey());matcher = pattern.matcher(e);if(matcher.matches()){error = m.getValue();break;}}}/*** 初始化錯誤的提示語*/private static void initMap() {// Java.lang.NullPointerException// java.lang.ClassNotFoundException// java.lang.ArithmeticException// java.lang.ArrayIndexOutOfBoundsException// java.lang.IllegalArgumentException// java.lang.IllegalAccessException// SecturityException// NumberFormatException// OutOfMemoryError // StackOverflowError // RuntimeException regexMap.put(".*NullPointerException.*", "嘿,無中生有~Boom!");regexMap.put(".*ClassNotFoundException.*", "你確定你能找得到它?");regexMap.put(".*ArithmeticException.*", "我猜你的數學是體育老師教的,對吧?");regexMap.put(".*ArrayIndexOutOfBoundsException.*", "恩,無下限=無節操,請不要跟我搭話");regexMap.put(".*IllegalArgumentException.*", "你的出生就是一場錯誤。");regexMap.put(".*IllegalAccessException.*", "很遺憾,你的信用卡賬號被凍結了,無權支付");regexMap.put(".*SecturityException.*", "死神馬上降臨");regexMap.put(".*NumberFormatException.*", "想要改變一下自己形象?去泰國吧,包你滿意");regexMap.put(".*OutOfMemoryError.*", "或許你該減減肥了");regexMap.put(".*StackOverflowError.*", "啊,啊,憋不住了!");regexMap.put(".*RuntimeException.*", "你的人生走錯了方向,重來吧");}}二、建立一個Application來全局監控
import android.app.Application;public class CrashApplication extends Application {@Overridepublic void onCreate() {super.onCreate();CrashHandler crashHandler = CrashHandler.getInstance();crashHandler.init(getApplicationContext());}} 最后在配置文件中加入注冊信息
<application android:name=".CrashApplication" ... />
和權限
<!--uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /--><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
提交錯誤日志到網絡服務器這一塊還沒有添加。如果添加了這一塊功能,就能夠實時的得要用戶使用時的錯誤日志,能夠及時反饋不同機型不同時候發生的錯誤,能對我們開發者的后期維護帶來極大的方便。
有關Android崩潰異常捕獲方法小編就給大家介紹這么多,希望對大家有所幫助!
新聞熱點
疑難解答