做任何軟件戚绕,都需要考慮異常情況的處理粉臊,這是軟件的可維護性的一部分。
異常崩潰是一種罕見的極端異常情況硬萍,這種情況下,針對終端用戶的UI反饋虎锚、事故設(shè)備的信息采集硫痰、向后臺維護人員的數(shù)據(jù)反饋等,都需要精心的設(shè)計窜护。
UI反饋
- 要做反饋效斑,首先要抓到所有的異常崩潰。
異常崩潰都是App進程的異常柱徙,每個App進程都運行在該App的Application中缓屠,所以我們可以在Application上集中抓到所有的進程異常:
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
private Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() {
if (uncaughtExceptionHandler == null) {
uncaughtExceptionHandler = CrashHandler.getInstance(this,this);
}
return uncaughtExceptionHandler;
}
private void init(){
Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler());
}
我們抓到所有的進程異常,然后統(tǒng)一拋給一個CrashHandler類去處理护侮,這個CrashHandler要實現(xiàn)UncaughtExceptionHandler接口:
public class CrashHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread thread, Throwable ex) {
}
}
- 盡力保持用戶數(shù)據(jù)的完整性敌完,并設(shè)法恢復崩潰前的界面。
如果要在崩潰時重啟App羊初,就需要在退出App前滨溉,再次StartActivity
Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivity(i);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
其中,android.os.Process.killProcess(Dalvik虛擬機方法)和System.exit(0)(常規(guī)java方法)起到的作用一樣长赞。
這里有一個關(guān)于Activity棧的陷阱晦攒,上面的代碼可以重啟入口Activity,但是得哆,如果這時你這個App的Activity棧里還有其他的Activity脯颜,這些Activity是仍然存在的,不會被銷毀贩据。
推薦的做法是在Application中維護一個Activity的列表伐脖,專門管理所有的Activity,在必要時乐设,通過這個列表讼庇,去銷毀所有的Activity
private ActivityStack stack;//擴展LinkList自定義一個activity列表
//用set注入
@Override
public void initActivityStack(ActivityStack stack) {
this.stack=stack;
}
//activity在oncreate時做添加//
@Override
public void addActivity(Activity activity) {
if(stack!=null)stack.addStack(activity);
}
//activity在ondestroy時做刪減//
@Override
public void removeActivity(Activity activity) {
if(stack!=null)stack.remove(activity);
}
@Override
public void clearAll() {
if(stack!=null)stack.clearAll();
}
在這個自定義的activity列表里,通過調(diào)用Activity的finish近尚,來銷毀Activity
public void clearAll() {
if(stack!=null&&stack.size()>0) {
for (Activity activity : stack) {
if (activity != null) {
activity.finish();
}
}
stack.clear();
}
}
注意蠕啄,為了避免Application持有Activity導致內(nèi)存泄露,在Activity的生命周期里不能只寫入列戈锻,還要記得寫出列歼跟。
- 然后要提示用戶發(fā)生了一些事情,這里要謹慎措辭格遭,最好根據(jù)產(chǎn)品特性設(shè)計一些符合產(chǎn)品氣質(zhì)的提示語哈街,這里就不展開了。
- 最后拒迅,在自動重啟和退出App之間尋找平衡骚秦,比如第一次崩潰當然可以自動重啟她倘,如果遇到特殊因素導致連續(xù)崩潰(如:接口問題或運行環(huán)境問題),就需要人為限制重啟的次數(shù)或頻率(比如作箍,記錄上次自動重啟的時間硬梁,判斷兩次重啟的時間間隔是否過窄),避免成為用戶眼中的流氓軟件胞得。
信息采集
對異常崩潰了解的越多荧止,就越容易處理它,所以我們要盡可能地采集相關(guān)信息阶剑。
對研發(fā)來說跃巡,最有用的當然是異常代碼行(如MainActivity第61行)和異常原因(如空指針異常),在研發(fā)環(huán)境里牧愁,我們可以通過logcat讀到這些信息素邪,那沒什么可說的,我們要考慮的是递宅,如果異常崩潰發(fā)生在萬里之外的生產(chǎn)環(huán)境,我們要怎樣采集信息苍狰。
- 首先办龄,要創(chuàng)建和保存本地log文件夾,專門保存這些信息淋昭,一方面俐填,網(wǎng)絡(luò)不是一直可靠的,保存到本地可以避免數(shù)據(jù)丟失翔忽;另一方面英融,無論是遠程數(shù)據(jù)上傳還是現(xiàn)場同僚手動拷貝,都需要有這樣一個文件夾歇式。
- 然后驶悟,要抓取異常代碼行和異常原因,也就是你在logcat里讀到的那些異常堆棧信息材失,所有的Java異常都會拋出一個Throwable痕鳍,這里面就能找到這些異常堆棧信息(需要用printwriter去讀)
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();
- 最后,有些崩潰是在特定的軟硬件環(huán)境下出現(xiàn)的龙巨,我們需要知道這些環(huán)境信息:
Map<String, String> infos = new HashMap<String, String>();
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
} catch (Exception e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
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");
}
數(shù)據(jù)反饋
首選當然是通過后臺網(wǎng)絡(luò)默默反饋(在WIFI環(huán)境下笼呆,避免消耗用戶流量)。
如果是以文件為單位上傳反饋旨别,只要做好鎖文件和銷毀文件即可诗赌。
如果是在線實時上傳反饋,就需要為每次崩潰編號秸弛,或根據(jù)編號依次上傳铭若,或在后臺進行合并過濾洪碳。
需要注意的是,本地日志文件不能過大奥喻,如果超過一定大小限制偶宫,要有自動清理機制,比如刪除日期最早的那個文件环鲤,是的纯趋,強烈建議根據(jù)日期來建立多個日志文件。