大家都知道,現(xiàn)在安裝 Android 系統(tǒng)的手機(jī)和設(shè)備千差萬(wàn)別,在模擬器上運(yùn)行良好的程序安裝到某款手機(jī)上說(shuō)不定就出現(xiàn)崩潰
的現(xiàn)象,開發(fā)者個(gè)人不可能購(gòu)買所有設(shè)備逐個(gè)調(diào)試,所以在程序發(fā)布出去之后,如果出現(xiàn)了崩潰現(xiàn)象,開發(fā)者應(yīng)該及時(shí)獲取在該設(shè)
備上導(dǎo)致崩潰的信息,這對(duì)于下一個(gè)版本的 BUG 修復(fù)幫助極大,所以今天就來(lái)介紹一下如何在程序崩潰的情況下收集相關(guān)的設(shè)備
參數(shù)信息和具體的異常信息,并發(fā)送這些信息到服務(wù)器供開發(fā)者分析和調(diào)試程序。
遇到軟件沒有捕獲的異常之后,系統(tǒng)會(huì)彈出這個(gè)默認(rèn)的強(qiáng)制關(guān)閉對(duì)話框。
我們當(dāng)然不希望用戶看到這種現(xiàn)象,簡(jiǎn)直是對(duì)用戶心靈上的打擊,而且對(duì)我們的 BUG 的修復(fù)也是毫無(wú)幫助的。我們需要的是軟
件有一個(gè)全局的異常捕獲器,當(dāng)出現(xiàn)一個(gè)我們沒有發(fā)現(xiàn)的異常時(shí),捕獲這個(gè)異常,并且將異常信息記錄下來(lái),上傳到服務(wù)器公開發(fā)
這分出現(xiàn)異常的具體原因。
接下來(lái)我們就來(lái)實(shí)現(xiàn)這一機(jī)制,不過(guò)首先我們還是來(lái)了解以下兩個(gè)類:android.app.application 和
java.lang.Thread.UncaughtExceptionHandler。
1、Application:用來(lái)管理應(yīng)用程序的全局狀態(tài)。在應(yīng)用程序啟動(dòng)時(shí) Application 會(huì)首先創(chuàng)建,然后才會(huì)根據(jù)情況(Intent)來(lái)啟動(dòng)
相應(yīng)的Activity 和 Service。本示例中將在自定義加強(qiáng)版的 Application 中注冊(cè)未捕獲異常處理器。
2、Thread.UncaughtExceptionHandler:線程未捕獲異常處理器,用來(lái)處理未捕獲異常。如果程序出現(xiàn)了未捕獲異常,默認(rèn)會(huì)彈
出系統(tǒng)中強(qiáng)制關(guān)閉對(duì)話框。我們需要實(shí)現(xiàn)此接口,并注冊(cè)為程序中默認(rèn)未捕獲異常處理。這樣當(dāng)未捕獲異常發(fā)生時(shí),就可以做一些
個(gè)性化的異常處理操作。
(1)新建java文件,CrashHandler.java 實(shí)現(xiàn)了 Thread.UncaughtExceptionHandler,使我們用來(lái)處理未捕獲異常的主要成員,代
碼如下:
public class CrashHandler implements UncaughtExceptionHandler { public static final String TAG = "CrashHandler"; // CrashHandler 實(shí)例 PRivate static CrashHandler INSTANCE = new CrashHandler(); // 程序的 Context 對(duì)象 private Context mContext; // 系統(tǒng)默認(rèn)的 UncaughtException 處理類 private Thread.UncaughtExceptionHandler mDefaultHandler; // 用來(lái)存儲(chǔ)設(shè)備信息和異常信息 private Map<String, String> infos = new HashMap<String, String>(); // 用于格式化日期,作為日志文件名的一部分 private DateFormat 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ù)來(lái)處理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && mDefaultHandler != null) { // 如果用戶沒有處理則讓系統(tǒng)默認(rèn)的異常處理器來(lái)處理 mDefaultHandler.uncaughtException(thread, ex); } else { try { Thread.sleep(3000); } catch (InterruptedException e) { Log.e(TAG, "error : ", e); } // 退出程序,注釋下面的重啟啟動(dòng)程序代碼 android.os.Process.killProcess(android.os.Process.myPid()); System.exit(1); // 重新啟動(dòng)程序,注釋上面的退出程序 Intent intent = new Intent(); intent.setClass(mContext,MainActivity.class); intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); android.os.Process.killProcess(android.os.Process.myPid()); } } /** * 自定義錯(cuò)誤處理,收集錯(cuò)誤信息,發(fā)送錯(cuò)誤報(bào)告等操作均在此完成 * * @param ex * @return true:如果處理了該異常信息;否則返回 false */ private boolean handleException(Throwable ex) { if (ex == null) { return false; } // 使用 Toast 來(lái)顯示異常信息 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 = new StringBuffer(); for (Map.Entry<String, String> 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(new Date()); 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; } } (2)完成這個(gè) CrashHandler 后,我們需要在一個(gè) Application 環(huán)境中讓其運(yùn)行,為此,我們繼承 android.app.Application,添加自己的代碼,CrashApplication.java代碼如下:package com.scott.crash; import android.app.Application; public class CrashApplication extends Application { @Override public void onCreate() { super.onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(getApplicationContext()); } }
(3)因?yàn)槲覀兩厦娴?CrashHandler 中,遇到異常后要保存設(shè)備參數(shù)和具體異常信息到 SDCARD,所以我們需要在
AndroidManifest.xml 中加入讀寫 SDCARD 權(quán)限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注