前言
本篇屬于《Android開發(fā)藝術(shù)探索》第13章使用CrashHandler獲取crash信息的學(xué)習(xí)筆記品姓,如有錯(cuò)誤距境,請(qǐng)指教习寸,不甚感激。
沒有一個(gè)App是完美的惦界,都是在不斷的完善修改bug中變的更好宴胧。特別是一些上線的app出現(xiàn)一些crash情況后,我們并不能及時(shí)的看到表锻,所以為了了解用戶在使用過程中出現(xiàn)了哪些crash信息恕齐,我們需要掌握crash信息收集技能。
基本思想
Google考慮到此情況瞬逊,在Thread類中提供了一個(gè)接口显歧,UncaughtExecptionHandler來提供我們使用,當(dāng)應(yīng)用發(fā)生了未捕獲的異常時(shí)确镊,系統(tǒng)會(huì)自動(dòng)調(diào)用這個(gè)接口的uncaughtException方法士骤。我們?cè)谶@個(gè)方法中可以保存異常信息到SD卡,然后在特定的時(shí)間將保存的文件上傳到web服務(wù)器蕾域,開發(fā)人員可以從服務(wù)器拉取文件拷肌,分析并修改相關(guān)的crash。
Demo代碼
自定義CrashHandler類并實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口
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() + "/CrashTest/log/";
private static final String FILE_NAME = "crash";
private static final String FILE_NAME_SUFFIX = ".trace";
private static CrashHandler sCrashHandler = new CrashHandler();
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
private CrashHandler(){}
// 單例模式
public static CrashHandler getInstance(){
return sCrashHandler;
}
public void init(Context context){
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this); //這兩句代碼不是很明白旨巷,有清楚的希望指教下
mContext = context.getApplicationContext();
}
/**
* 這是最關(guān)鍵的函數(shù)巨缘,當(dāng)程序中有未捕獲的異常,系統(tǒng)將會(huì)自動(dòng)調(diào)用此方法
* @param thread 為出現(xiàn)未捕獲異常的線程
* @param exception 未捕獲的異常采呐,有了此異常若锁,我們就能得到異常信息
*/
@Override
public void uncaughtException(Thread thread, Throwable exception) {
try {
//保存異常信息到sd卡
saveExceptionToSDCard(exception);
//上傳異常信息到服務(wù)器
uploadExceptionToServer(exception);
} catch (IOException e) {
e.printStackTrace();
}
// 如果系統(tǒng)提供了默認(rèn)的異常處理器,就交給系統(tǒng)自己處理斧吐,否則就自己結(jié)束掉自己
if (mDefaultCrashHandler!=null){
mDefaultCrashHandler.uncaughtException(thread,exception);
}else {
Process.killProcess(Process.myPid());
}
}
// 將異常信息保存到SDCard
private void saveExceptionToSDCard(Throwable ex) throws IOException {
// 如果SD卡不存在或無法使用又固,則無法寫入異常信息,給與提示
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
if (DEBUG){
Log.w(TAG,"sdcard unmounted , skip save exception"); //sd卡未安裝好煤率,跳出存儲(chǔ)異常
}
return;
}
// 文件存儲(chǔ)路徑
File dir = new File(PATH);
if (!dir.exists()){
dir.mkdirs(); // 這里開始自己犯了一個(gè)低級(jí)錯(cuò)誤仰冠,寫成了dir.mkdir(),需要注意蝶糯;
}
// 獲取當(dāng)前時(shí)間
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
// 創(chuàng)建存儲(chǔ)異常信息的文件
File file = new File(PATH+FILE_NAME+time+FILE_NAME_SUFFIX);
if (!file.exists()){
file.createNewFile();
}
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
pw.println(time);
savePhoneInfo(pw);
pw.println();
ex.printStackTrace(pw); //輸出異常信息
pw.close();
}catch (PackageManager.NameNotFoundException e){
Log.e(TAG,"save crash info failed");
}
}
// 保存手機(jī)的信息
private void savePhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
// APP的版本信息
pw.print("APP Version:");
pw.print(pi.versionName);
pw.print('_');
pw.println(pi.versionCode);
// Android 手機(jī)版本號(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("CUP ABI:");
pw.println(Build.CPU_ABI);
}
// 將異常信息上傳到服務(wù)器
private void uploadExceptionToServer(Throwable ex){
//Error error = new Error(ex.getMessage());
// 上傳服務(wù)器的操作還不清楚洋只,希望有會(huì)的指教下
}
}
項(xiàng)目中怎樣使用自己的CrashHandler類呢?只需要在應(yīng)用application初始化的時(shí)候?yàn)榫€程設(shè)置CrashHandler即可:
public class MyApplication extends Application {
private static MyApplication sMyApplication;
@Override
public void onCreate() {
super.onCreate();
// 在這里為應(yīng)用設(shè)置異常處理,然后程序才能獲取未處理的異常
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
public static MyApplication getInstance(){
return sMyApplication;
}
}
最后我們模擬一個(gè)異常,看下結(jié)果:
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button)findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
throw new RuntimeException("自己拋出一個(gè)異衬菊牛……");
}
});
}
}
程序運(yùn)行后,我們點(diǎn)擊button后端三,程序就因?yàn)楫惓M顺鱿侠瘛_@是我們可以在手機(jī)上下載一個(gè)ES文件瀏覽器,搜索我們的文件夾名CrashTest——>log: