捕獲Android中未捕獲到的異常,防止程序崩潰界面不友好

序言:本篇的筆記篇刽严,用于記錄我目前使用的異常處理器昂灵,以便后期查找使用;

在Android開發(fā)中,我們的程序偶爾會出現(xiàn)“很抱歉舞萄,XXXX已停止運行”眨补,這是因為我們雖然在開發(fā)中盡力避免了RuntimeException,但還有有未捕獲到的異常倒脓,這時就需要一個全局的異常捕獲器撑螺,雖不能避免,但至少對用戶友好些把还,而且此時可以收集到Exception信息实蓬,便于我們定位和解決問題茸俭。建議直接收藏此文,在實際開發(fā)中直接使用就可以了

import android.content.Context;
import android.content.Intent;
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.os.StatFs;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * 記錄程序崩潰的日志 UncaughtException處理類,當程序發(fā)生Uncaught異常的時候,由該類來接管程序,并記錄發(fā)送錯誤報告.
 *
 * @author ljp
 */
public class CrashHandler implements UncaughtExceptionHandler {

    private static final String TAG = "CrashHandler";
    private Thread.UncaughtExceptionHandler mDefaultHandler;// 系統(tǒng)默認的UncaughtException處理類
    private volatile static CrashHandler INSTANCE;// CrashHandler實例
    private Context mContext;// 程序的Context對象
    private Map<String, String> info = new HashMap<String, String>();// 用來存儲設(shè)備信息和異常信息
    private File file;
    private static final long FILESIZE = 1024 * 1024 * 30;// 文件的大小為30M安皱;
    private static final String FILENAME = "CrashLog.txt";

    /**
     * 保證只有一個CrashHandler實例
     */
    private CrashHandler(Context context) {
        mContext = context.getApplicationContext();
        file = getSaveFile();
        if (file.exists() && file.isFile() && file.length() > FILESIZE) {
            file.delete();
        }
    }

    /**
     * 獲取保存日志文件的路徑
     *
     * @return
     */
    private File getSaveFile() {
        File file = null;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//SD卡可用
            File path = new File(ConsValue.FilePath.logcatPath);
            if (getUsableSpace(path) >= FILESIZE) {
                file = new File(path, FILENAME);
            } else {
                file = new File(mContext.getCacheDir(), FILENAME);
            }
        } else {
            file = new File(mContext.getCacheDir(), FILENAME);
        }
        return file;
    }

    /**
     * 獲取指定路徑下的可用空間大小
     *
     * @param path
     * @return
     */
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        StatFs statFs = new StatFs(path.getPath());
        return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
    }

    /**
     * 獲取CrashHandler實例 ,單例模式
     */
    public static CrashHandler getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (CrashHandler.class) {
                if (INSTANCE == null) {
                    if (context == null) {
                        throw new NullPointerException("context cannet NULL");
                    }
                    INSTANCE = new CrashHandler(context);
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 初始化
     */
    public void init() {
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 獲取系統(tǒng)默認的UncaughtException處理器
        Thread.setDefaultUncaughtExceptionHandler(this);// 設(shè)置該CrashHandler為程序的默認處理器
    }

    /**
     * 當UncaughtException發(fā)生時會轉(zhuǎn)入該重寫的方法來處理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            mDefaultHandler.uncaughtException(thread, ex); // 如果自定義的沒有處理則讓系統(tǒng)默認的異常處理器來處理
        } else {
            SystemClock.sleep(3000);// 如果處理了调鬓,讓程序繼續(xù)運行3秒再退出,保證文件保存并上傳到服務(wù)器
            Utils.exitProgress();// 退出程序
        }
    }

    /**
     * 自定義錯誤處理,收集錯誤信息 發(fā)送錯誤報告等操作均在此完成.
     *
     * @param ex 異常信息
     * @return true 如果處理了該異常信息;否則返回false.
     */
    public boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        new Thread() {
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出現(xiàn)異常,即將退出", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        collectDeviceInfo(mContext); // 收集設(shè)備參數(shù)信息
        saveCrashInfo2File(ex);// 保存日志文件
        uploadExceptionToServer();//上傳數(shù)據(jù)到服務(wù)器
        return true;
    }


    private void uploadExceptionToServer() {
        //TODO 發(fā)送數(shù)據(jù)到服務(wù)器
    }

    /**
     * 設(shè)備的詳細版本號酌伊,如:1.0.0腾窝,對應(yīng)manifest或moude的build.gradle中的 versionName
     */
    private final String KEY_versionName = "versionName";
    /**
     * 設(shè)備大的版本號,如:1居砖,對應(yīng)manifest或moude的build.gradle中的 versionCode
     */
    private final String KEY_versionCode = "versionCode";
    /**
     * 系統(tǒng)的版本號虹脯,如:4.4.4
     */
    private final String KEY_osVersion = "osVersion";
    /**
     * 系統(tǒng)對應(yīng)的SDK編碼,如:19
     */
    private final String KEY_osSDKVersion = "osSDKVersion";
    /**
     * 手機制造廠商奏候,如:HUAWEI
     */
    private final String KEY_phoneMaker = "phoneMaker";
    /**
     * 手機品牌循集,如:Honor
     */
    private final String KEY_BRAND = "brand";
    /**
     * 手機型號,如:Che1-CL10
     */
    private final String KEY_phoneModel = "phoneModel";
    /**
     * CPU架構(gòu)蔗草,如:armeabi-v7a
     */
    private final String KEY_cupAbi = "cupAbi";

    /**
     * 收集設(shè)備參數(shù)信息
     *
     * @param context
     */
    public void collectDeviceInfo(Context context) {
        try {
            PackageManager pm = context.getPackageManager();// 獲得包管理器
            PackageInfo pi = pm.getPackageInfo(context.getPackageName(),
                    PackageManager.GET_ACTIVITIES);// 得到該應(yīng)用的信息咒彤,即主Activity
            if (pi != null) {
                String versionName = pi.versionName == null ? "null"
                        : pi.versionName;
                String versionCode = pi.versionCode + "";
                info.put(KEY_versionName, versionName);
                info.put(KEY_versionCode, versionCode);
            }
            info.put(KEY_osVersion, Build.VERSION.RELEASE);
            info.put(KEY_osSDKVersion, "" + Build.VERSION.SDK_INT);
            info.put(KEY_phoneMaker, Build.MANUFACTURER);
            info.put(KEY_BRAND, Build.BRAND);
            info.put(KEY_phoneModel, Build.MODEL);
            info.put(KEY_cupAbi, Build.CPU_ABI);
//            Build.DISPLAY;//版本號,如:Che1-CL10V100R001CHNC92B285
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        //遍歷所有 Build中的字段
        Field[] fields = Build.class.getDeclaredFields();// 反射機制
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                info.put(field.getName(), field.get("").toString());
                Log.d(TAG, field.getName() + ":" + field.get(""));
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    private void saveCrashInfo2File(Throwable ex) {
        StringBuffer sb = new StringBuffer();
        sb.append(DateUtil.getDateEN() + ":發(fā)生崩潰的異常咒精,設(shè)備的信息如下:******************************************************分割線***********************" + "\r\n");
        for (Map.Entry<String, String> entry : info.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "\t=\t" + value + "\r\n");
        }
        Writer writer = new StringWriter();
        PrintWriter pw = new PrintWriter(writer);
        ex.printStackTrace(pw);
        Throwable cause = ex.getCause();
        // 循環(huán)著把所有的異常信息寫入writer中
        while (cause != null) {
            cause.printStackTrace(pw);
            cause = cause.getCause();
        }
        pw.close();// 記得關(guān)閉
        String result = writer.toString();
        sb.append("發(fā)生崩潰的異常信息如下:" + "\r\n");
        sb.append(result);
        Log.e(TAG, result);
        // 保存文件
        try {
            //判斷文件夾是否存在
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdir();
            }
            FileOutputStream fos = new FileOutputStream(file, true);
            fos.write(sb.toString().getBytes("UTF-8"));
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用時镶柱,一般在Application中初始化調(diào)用就可以了

 CrashHandler.getInstance(mContext).init();

強烈建議收藏使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市模叙,隨后出現(xiàn)的幾起案子歇拆,更是在濱河造成了極大的恐慌,老刑警劉巖范咨,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件故觅,死亡現(xiàn)場離奇詭異,居然都是意外死亡湖蜕,警方通過查閱死者的電腦和手機逻卖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昭抒,“玉大人评也,你說我怎么就攤上這事∶鸱担” “怎么了盗迟?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長熙含。 經(jīng)常有香客問我罚缕,道長,這世上最難降的妖魔是什么怎静? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任邮弹,我火速辦了婚禮黔衡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腌乡。我一直安慰自己盟劫,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布与纽。 她就那樣靜靜地躺著侣签,像睡著了一般。 火紅的嫁衣襯著肌膚如雪急迂。 梳的紋絲不亂的頭發(fā)上影所,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音僚碎,去河邊找鬼猴娩。 笑死,一個胖子當著我的面吹牛听盖,可吹牛的內(nèi)容都是我干的胀溺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼皆看,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了背零?” 一聲冷哼從身側(cè)響起腰吟,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徙瓶,沒想到半個月后毛雇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡侦镇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年灵疮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壳繁。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡震捣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闹炉,到底是詐尸還是另有隱情蒿赢,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布渣触,位于F島的核電站羡棵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅钻。R本人自食惡果不足惜皂冰,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一店展、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秃流,春花似錦壁查、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峻贮,卻和暖如春席怪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纤控。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工挂捻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人船万。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓刻撒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耿导。 傳聞我的和親對象是個殘疾皇子声怔,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353