Android使用CrashHandler來(lái)獲取應(yīng)用的crash信息的方法
在日常開(kāi)發(fā)的過(guò)程中應(yīng)該不可避免的會(huì)發(fā)生 crash,無(wú)論你的程序?qū)懙亩嗝赐昝?,都不可能完全避?crash 的發(fā)生,可能是由于 Android 底層的 bug,也可能是由于不充分的機(jī)型適配或者是糟糕的網(wǎng)絡(luò)狀況。當(dāng) crash 發(fā)生時(shí),系統(tǒng)就會(huì)kill掉正在執(zhí)行的程序,現(xiàn)象就是閃退,或者提醒用戶(hù)程序已經(jīng)停止運(yùn)行,這對(duì)用戶(hù)來(lái)說(shuō)是很不友好的,也是我們不愿意看到的,更早的是當(dāng)用戶(hù)發(fā)生 crash,我們開(kāi)發(fā)者卻無(wú)法得知程序?yàn)楹?crash,即便我們想去解決這個(gè) bug,但是由于無(wú)法知道用戶(hù)當(dāng)時(shí)的 crash 信息,所以往往也無(wú)能為力,幸運(yùn)的是,Andorid 提供了處理這類(lèi)問(wèn)題的方法,接下來(lái)我們就來(lái)一起看看到底 Android 給我們提供了什么方法來(lái)解決這個(gè)棘手的問(wèn)題
一、Thread 類(lèi)中的 setDefaultUncaughtExceptionHandler
/**
* Sets the default uncaught exception handler. This handler is invoked in
* case any Thread dies due to an unhandled exception.
*
* @param handler
* The handler to set or null.
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
Thread.defaultUncaughtHandler = handler;
}
這個(gè)方法其實(shí)就可以解決我們應(yīng)用程序的 crash 問(wèn)題,設(shè)置系統(tǒng)默認(rèn)異常處理器,當(dāng)系統(tǒng)發(fā)生crash 時(shí),系統(tǒng)就會(huì)回調(diào) UncaughtExceptionHandler 的 uncaughtException 方法,在 uncaughtException 方法中就可以獲取到異常信息,可以選擇把異常信息存儲(chǔ)下來(lái),存儲(chǔ)方式大家可以自己選擇,然后在合適的時(shí)候通過(guò)網(wǎng)絡(luò)將 crash 信息上傳到服務(wù)器上,這樣我們開(kāi)發(fā)人員就可以分析用戶(hù) crash 的場(chǎng)景從而在后面的版本中進(jìn)行修復(fù),我們還可以在發(fā)生 crash 發(fā)生時(shí)彈出一個(gè)對(duì)話(huà)框,告訴用戶(hù)程序 crash 了,然后再退出
二、實(shí)現(xiàn)自己的異常捕獲類(lèi)
1)建立異常 Handler,命名為 CrashHandler,代碼如下
/**
* 異常捕獲類(lèi)
* Created by qiudengjiao on 2017/9/29.
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/ryg_test/log/";
private static final String FILE_NAME = "crash";
//log文件的后綴名
private static final String FILE_NAME_SUFFIX = ".trace";
private static CrashHandler sInstance = new CrashHandler();
//系統(tǒng)默認(rèn)的異常處理(默認(rèn)情況下,系統(tǒng)會(huì)終止當(dāng)前的異常程序)
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
//構(gòu)造方法私有,防止外部構(gòu)造多個(gè)實(shí)例
private CrashHandler() {
}
public static CrashHandler getInstance() {
return sInstance;
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
//獲取系統(tǒng)默認(rèn)的異常處理器
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
//將當(dāng)前實(shí)例設(shè)為系統(tǒng)默認(rèn)的異常處理器
Thread.setDefaultUncaughtExceptionHandler(this);
//獲取Context,方便內(nèi)部使用
mContext = context.getApplicationContext();
}
/**
* 這個(gè)是最關(guān)鍵的函數(shù),當(dāng)程序中有未被捕獲的異常,系統(tǒng)將會(huì)自動(dòng)調(diào)用#uncaughtException方法
* thread為出現(xiàn)未捕獲異常的線程,ex為未捕獲的異常,有了這個(gè)throwable,我們就可以得到異常信息
*
* @param thread
* @param throwable
*/
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
//導(dǎo)出異常信息到SD卡中
dumpExceptionToSDCard(throwable);
//這里可以通過(guò)網(wǎng)絡(luò)上傳異常信息到服務(wù)器,便于開(kāi)發(fā)人員分析日志從而解決bug
uploadExceptionToServer();
} catch (IOException e) {
e.printStackTrace();
}
//打印出當(dāng)前調(diào)用棧信息
throwable.printStackTrace();
//如果系統(tǒng)提供了默認(rèn)的異常處理器,則交給系統(tǒng)去結(jié)束我們的程序,否則就由我們自己結(jié)束自己
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, throwable);
} else {
android.os.Process.killProcess(android.os.Process.myPid());
}
}
/**
* 保存到內(nèi)存卡
* 這里我們也可以根據(jù)項(xiàng)目需要選擇其他的保存方式
*
* @param throwable
* @throws IOException
*/
private void dumpExceptionToSDCard(Throwable throwable) throws IOException {
//如果SD卡不存在或無(wú)法使用,則無(wú)法把異常信息寫(xiě)入SD卡
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted,skip dump exception");
return;
}
}
File dir = new File(PATH);
if (!dir.exists()) {
dir.mkdirs();
}
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
//以當(dāng)前時(shí)間創(chuàng)建log文件
File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//導(dǎo)出發(fā)生異常的時(shí)間
pw.println(time);
//導(dǎo)出手機(jī)信息
dumpPhoneInfo(pw);
pw.println();
//導(dǎo)出異常的調(diào)用棧信息
throwable.printStackTrace(pw);
pw.close();
} catch (Exception e) {
Log.e(TAG, "dump crash info failed");
}
}
/**
* 收集設(shè)備參數(shù)信息
*
* @param pw
* @throws PackageManager.NameNotFoundException
*/
private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
//應(yīng)用的版本名稱(chēng)和版本號(hào)
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print('_');
pw.println(pi.versionCode);
//android版本號(hào)
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
//手機(jī)制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手機(jī)型號(hào)
pw.print("Model: ");
pw.println(Build.MODEL);
//cpu架構(gòu)
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
}
/**
* 將異常信息上傳到服務(wù)器
*/
private void uploadExceptionToServer() {
//在這里寫(xiě)上傳到服務(wù)器的邏輯
}
}
從上面的代碼可以看出,當(dāng)應(yīng)用程序崩潰時(shí),CrashHandler 類(lèi)會(huì)將異常信息以及設(shè)備信息寫(xiě)入 SD 卡,這里大家也可以根據(jù)自己項(xiàng)目需要進(jìn)行處理,例如也可以存儲(chǔ)在數(shù)據(jù)庫(kù)中,接著將異常交給系統(tǒng)處理,系統(tǒng)會(huì)幫我們中止程序,如果系統(tǒng)沒(méi)有默認(rèn)的異常處理機(jī)制,那么就自行中止,當(dāng)然而又可以選擇將異常信息上傳到服務(wù)器,這里我們沒(méi)有實(shí)現(xiàn)這個(gè)邏輯,實(shí)際開(kāi)發(fā)中都需要將異常信息上傳到服務(wù)器
三、如何使用 CrashHandler
其實(shí)使用 CrashHandler 也非常簡(jiǎn)單,我們可以在 Application 初始化的時(shí)候來(lái)設(shè)置 CrashHandler,如下所示:
/**
* 自定義 Application 類(lèi)
* Created by qiudengjiao on 2017/9/29.
*/
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
init();
}
private void init() {
//初始化異常捕獲類(lèi) CrashHandler
CrashHandler.getInstance().init(this);
}
}
通過(guò)上面的操作,我們的程序就能捕獲到 crash 了,同時(shí)還能從服務(wù)器上查看用戶(hù)的 crash 信息,今天就寫(xiě)到這里,給大家推薦一本不錯(cuò)的書(shū):Android 開(kāi)發(fā)藝術(shù),作者是任玉剛,相信大家也都知道,這本書(shū)的內(nèi)容還是非常不錯(cuò)的,值得大家一看,比較適合有一定 Android 基礎(chǔ)的同學(xué),馬上就是國(guó)慶小長(zhǎng)假了,祝大家國(guó)慶節(jié)愉快
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
flutter的環(huán)境安裝配置問(wèn)題及解決方法
Flutter是Google推出的基于Dart語(yǔ)言開(kāi)發(fā)的跨平臺(tái)開(kāi)源UI框架,旨在統(tǒng)一紛紛擾擾的跨平臺(tái)開(kāi)發(fā)框架,在UI層面上多端共用一套Dart代碼來(lái)實(shí)現(xiàn)多平臺(tái)適配開(kāi)發(fā),這篇文章主要介紹了flutter的環(huán)境安裝配置問(wèn)題,需要的朋友可以參考下2020-06-06
Android編程開(kāi)發(fā)從零開(kāi)始編寫(xiě)一個(gè)輕量級(jí)瀏覽器
這篇文章主要為大家介紹了Android編程開(kāi)發(fā)從零開(kāi)始編寫(xiě)一個(gè)輕量級(jí)瀏覽器過(guò)程步驟示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步2022-02-02
Android?registerForActivityResult新用法實(shí)現(xiàn)兩個(gè)Activity間數(shù)據(jù)傳遞
這篇文章主要為大家介紹了Android?registerForActivityResult新用法實(shí)現(xiàn)兩個(gè)Activity間數(shù)據(jù)傳遞詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Android編程實(shí)現(xiàn)簡(jiǎn)易彈幕效果示例【附demo源碼下載】
這篇文章主要介紹了Android編程實(shí)現(xiàn)簡(jiǎn)易彈幕效果,涉及Android動(dòng)畫(huà)及視圖動(dòng)態(tài)操作相關(guān)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2017-01-01
Android實(shí)現(xiàn)漂亮的Gallery畫(huà)廊
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)漂亮的Gallery畫(huà)廊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Android仿微信照片選擇器實(shí)現(xiàn)預(yù)覽查看圖片
這篇文章主要介紹了Android仿微信照片選擇器實(shí)現(xiàn)預(yù)覽查看圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-01-01
Android 如何使用SQLite保存數(shù)據(jù)
對(duì)于重復(fù)數(shù)據(jù)或結(jié)構(gòu)化數(shù)據(jù)(例如聯(lián)系信息),將數(shù)據(jù)保存到數(shù)據(jù)庫(kù)是理想選擇,SQL 數(shù)據(jù)庫(kù)的主要原則之一是架構(gòu),即數(shù)據(jù)庫(kù)組織方式的正式聲明,本篇文章介紹在 Android 上使用 SQLite 數(shù)據(jù)庫(kù),感興趣的朋友一起看看吧2024-03-03
Android畫(huà)圖并保存圖片的具體實(shí)現(xiàn)代碼
這篇文章介紹了在Android中畫(huà)圖并保存圖片的實(shí)例,以下是具體的實(shí)現(xiàn)方法,有需要的朋友可以參考一下2013-07-07

