大家都知道饶米,現(xiàn)在安裝Android系統(tǒng)的手機(jī)版本和設(shè)備千差萬(wàn)別详瑞,在模擬器和自己的android手機(jī)上運(yùn)行良好的程序安裝到某款手機(jī)上說不定就出現(xiàn)崩潰的現(xiàn)象摘刑,或是本來好好的程序裝到領(lǐng)導(dǎo)或老板的手機(jī)上就崩潰了伟葫,這可是很尷尬很致命的裹芝,開發(fā)者個(gè)人不可能購(gòu)買所有設(shè)備逐個(gè)調(diào)試部逮,領(lǐng)導(dǎo)們也不可能把手機(jī)拿給你通過數(shù)據(jù)線進(jìn)行調(diào)試,所以在程序發(fā)布出去之后嫂易,如果出現(xiàn)了崩潰現(xiàn)象兄朋,開發(fā)者應(yīng)該及時(shí)獲取在該設(shè)備上導(dǎo)致崩潰的信息,這對(duì)于下一個(gè)版本的bug修復(fù)幫助極大怜械,所以今天就來介紹一下如何在程序崩潰的情況下收集相關(guān)的設(shè)備參數(shù)信息和具體的異常信息颅和,并發(fā)送這些信息到服務(wù)器供開發(fā)者分析和調(diào)試程序。
詳細(xì)操作:
1缕允、創(chuàng)建一個(gè)CrashHandler.java
importjava.io.File;
importjava.io.FileOutputStream;
importjava.io.PrintWriter;
importjava.io.StringWriter;
importjava.io.Writer;
importjava.lang.Thread.UncaughtExceptionHandler;
importjava.lang.reflect.Field;
importjava.text.DateFormat;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.HashMap;
importjava.util.Map;
importandroid.content.Context;
importandroid.content.pm.PackageInfo;
importandroid.content.pm.PackageManager;
importandroid.content.pm.PackageManager.NameNotFoundException;
importandroid.os.Build;
importandroid.os.Environment;
importandroid.os.Looper;
importandroid.util.Log;
importandroid.widget.Toast;
/**
*?UncaughtException處理類,當(dāng)程序發(fā)生Uncaught異常的時(shí)候,有該類來接管程序,并記錄發(fā)送錯(cuò)誤報(bào)告.
*/
public class CrashHandler implements UncaughtExceptionHandler?{
public static final String?TAG?="CrashHandler";
//系統(tǒng)默認(rèn)的UncaughtException處理類
private Thread.UncaughtExceptionHandler?mDefaultHandler;
//CrashHandler實(shí)例
privatestaticCrashHandler?INSTANCE?=newCrashHandler();
//程序的Context對(duì)象
private Context?mContext;
//用來存儲(chǔ)設(shè)備信息和異常信息
private Map?infos?=new HashMap();
//用于格式化日期,作為日志文件名的一部分
private Date Format?formatter?=new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
/**?保證只有一個(gè)CrashHandler實(shí)例?*/
private CrashHandler()?{
}
/**?獲取CrashHandler實(shí)例?,單例模式?*/
public static CrashHandler?getInstance()?{
? ? ? ? ? ? return INSTANCE;
}
/**
*?初始化
*
*?@param?context
*/
public void init(Context?context)?{
? ? ? ? ? ? mContext?=?context;
? ? ? ? ? ? //獲取系統(tǒng)默認(rèn)的UncaughtException處理器
? ? ? ? ? ? mDefaultHandler?=?Thread.getDefaultUncaughtExceptionHandler();
? ? ? ? ? ? //設(shè)置該CrashHandler為程序的默認(rèn)處理器
? ? ? ? ? ? Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
*?當(dāng)UncaughtException發(fā)生時(shí)會(huì)轉(zhuǎn)入該函數(shù)來處理
*/
@Override
public void uncaughtException(Thread?thread,?Throwable?ex)?{
? ? ? ? ? ? if(!handleException(ex)?&&?mDefaultHandler?!=null)?{
? ? ? ? ? ? ? ? ? ? ? ?//如果用戶沒有處理則讓系統(tǒng)默認(rèn)的異常處理器來處理
? ? ? ? ? ? ? ? ? ? ? ?mDefaultHandler.uncaughtException(thread,?ex);
? ? ? ? ? ? ?}else{
? ? ? ? ? ? ? ? ? ? ? ?try{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(3000);
? ? ? ? ? ? ? ? ? ? ? ? ?}catch(InterruptedException?e)?{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Log.e(TAG,"error?:?",?e);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? //退出程序
? ? ? ? ? ? ? ? ? ? ? ? android.os.Process.killProcess(android.os.Process.myPid());
? ? ? ? ? ? ? ? ? ? ? ? System.exit(1);
? ? ? ? ? ? ? }
}
/**
*?自定義錯(cuò)誤處理,收集錯(cuò)誤信息?發(fā)送錯(cuò)誤報(bào)告等操作均在此完成.
*
*?@param?ex
*?@return?true:如果處理了該異常信息;否則返回false.
*/
private boolean handleException(Throwable?ex)?{
? ? ? ? ? ? ? if(ex?==null)?{
? ? ? ? ? ? ? ? ? ? ? ?return false;
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? //使用Toast來顯示異常信息
? ? ? ? ? ? ? new Thread() {
? ? ? ? ? ? ? ? ? ? ? ?@Override
? ? ? ? ? ? ? ? ? ? ? ?public void run() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Looper.prepare();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Toast.makeText(mContext,"很抱歉,程序出現(xiàn)異常,即將退出.", Toast.LENGTH_LONG).show();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Looper.loop();
? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?}.start();
? ? ? ? ? ? ? ?//收集設(shè)備參數(shù)信息
? ? ? ? ? ? ? ?collectDeviceInfo(mContext);
? ? ? ? ? ? ? ?//保存日志文件
? ? ? ? ? ? ? saveCrashInfo2File(ex);
? ? ? ? ? ? ?return true;
}
/**
*?收集設(shè)備參數(shù)信息
*?@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);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ?}
}
/**
*?保存錯(cuò)誤信息到文件中
*
*?@param?ex
*?@return??返回文件名稱,便于將文件傳送到服務(wù)器
*/
private String?saveCrashInfo2File(Throwable?ex)?{
? ? ? ? ? ? ? StringBuffer?sb?=newStringBuffer();
? ? ? ? ? ? ? for(Map.Entry?entry?:?infos.entrySet())?{
? ? ? ? ? ? ? ? ? ? ? ?String?key?=?entry.getKey();
? ? ? ? ? ? ? ? ? ? ? String?value?=?entry.getValue();
? ? ? ? ? ? ? ? ? ? ? sb.append(key?+"="+?value?+"\n");
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?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(newDate());
? ? ? ? ? ? ? ? ? ? ? ?String?fileName?="crash-"+?time?+"-"+?timestamp?+".log";
? ? ? ? ? ? ? ? ? ? ? ?if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))?{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?String?path?="/sdcard/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;
? ? ? ? ? }
}
在收集異常信息時(shí)峡扩,朋友們也可以使用Properties,因?yàn)镻roperties有一個(gè)很便捷的方法properties.store(OutputStream out, String comments)障本,用來將Properties實(shí)例中的鍵值對(duì)外輸?shù)捷敵隽髦杏卸睿窃谑褂玫倪^程中發(fā)現(xiàn)生成的文件中異常信息打印在同一行,看起來極為費(fèi)勁彼绷,所以換成Map來存放這些信息巍佑,然后生成文件時(shí)稍加了些操作。
2寄悯、完成這個(gè)CrashHandler后萤衰,我們需要在一個(gè)Application環(huán)境中讓其運(yùn)行,為此猜旬,我們繼承android.app.Application脆栋,在Application的onCreate()中加入以下兩行代碼,便可啟動(dòng)全局異常捕獲
//啟動(dòng)全局獲取異常
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(getApplicationContext());
最后洒擦,為了讓我們的CrashApplication取代android.app.Application的地位椿争,在我們的代碼中生效,我們需要修改AndroidManifest.xml:
<application android:name="你的Application類名" />
因?yàn)槲覀僀rashHandler中熟嫩,遇到異常后要保存設(shè)備參數(shù)和具體異常信息到SDCARD秦踪,所以我們需要在AndroidManifest.xml中加入讀寫SDCARD權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />