編輯:關於Android編程
在實際項目開發中,會出現很多的異常直接導致程序crash掉,在開發中我們可以通過logcat查看錯誤日志,Debug出現的異常,讓程序安全的運行,但是在開發中有些異常隱藏的比較深,直到項目發布後,由於各種原因,譬如android設備不一致等等,android版本不同,實際上我們在測試的時候不可能在市場上所有的Android設備上都做了測試,當用戶安裝使用時被暴露出來,導致程序直接crash掉,這顯然對於用戶是不OK的!這些在用戶設備上導致crash的異常我們是不知道的,要想知道這些異常出現的一些信息,我們還是得自己通過程序捕獲到異常,並且將其記錄下來(本地保存或者上傳服務器),方便項目維護。
先來看一下,我自己“故意”定義出來的一個異常,在MainActivity,java中:
package com.example.crash; import android.os.Bundle; import android.app.Activity; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); int i = 1; System.out.println(i/0); } }以上程序報出一個數學運算的除0異常,顯然程序被“崩潰”,不能繼續執行的。看一下運行效果截圖:
運行結果如上圖所示,這種直接崩潰的效果對於用戶來說是很不OK的,用戶不知道發生了什麼,程序就停止了,會讓用戶對程序有種不想繼續使用的想法。 對於程序中未捕獲的異常,我們可以做哪些操作呢!我們需要的是軟件有一個全局的異常捕獲器,當出現一個我們沒有發現的異常時,捕獲這個異常,並且將異常信息記錄下來,上傳到服務器公開發這分析出現異常的具體原因,這是一種最佳實踐,那麼我們接下來就必須要熟悉兩個類別,一個是android提供的Application,另一個是Java提供的Thread.UncaughtExceptionHandler。
Application:這是android程序管理全局狀態的類,Application在程序啟動的時候首先被創建出來,它被用來統一管理activity、service、broadcastreceiver、contentprovider四大組件以及其他android元素,這裡可以打開android工程下的Mainifest.xml文件查看一下。我們除了使用android默認的Application來處理程序,也可以自定義一個Application處理一些需要在全局狀態下控制程序的操作,例如本文講到的處理程序未知異常時,這是一種最佳實踐。
Thread.UncaughtExceptionHandler:關於這個概念的解釋,我在JDK1.6的文檔中找到一些科學的解釋。
當 Thread 因未捕獲的異常而突然終止時,調用處理程序的接口。
當某一線程因未捕獲的異常而即將終止時,Java 虛擬機將使用 Thread.getUncaughtExceptionHandler() 查詢該線程以獲得其 UncaughtExceptionHandler 的線程,並調用處理程序的 uncaughtException 方法,將線程和異常作為參數傳遞。如果某一線程沒有明確設置其 UncaughtExceptionHandler,則將它的 ThreadGroup 對象作為其 UncaughtExceptionHandler。如果 ThreadGroup 對象對處理異常沒有什麼特殊要求,那麼它可以將調用轉發給默認的未捕獲異常處理程序。
Thread.UncaughtExceptionHandler是一個接口,它提供如下的方法,讓我們自定義處理程序。
void uncaughtException(Thread t,Throwable e)
當給定線程因給定的未捕獲異常而終止時,調用該方法。Java 虛擬機將忽略該方法拋出的任何異常。參數:t - 線程 e - 異常
一句話,線程未捕獲異常處理器,用來處理未捕獲異常。如果程序出現了未捕獲異常,默認會彈出系統中強制關閉對話框。我們需要實現此接口,並注冊為程序中默認未捕獲異常處理。這樣當未捕獲異常發生時,就可以做一些個性化的異常處理操作。所以接下來,我們要做的就是自定義一個CrashHandler類去實現Thread.UncaughtExceptionHandler,並且在實現的方法中做一些相關的操作。
package com.example.crash; import java.io.File; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Environment; import android.os.Looper; import android.util.Log; import android.widget.Toast; public class CrashHandler implements UncaughtExceptionHandler { public static final String TAG = CrashHandler; // 系統默認的UncaughtException處理類 private Thread.UncaughtExceptionHandler mDefaultHandler; // CrashHandler實例 private static CrashHandler INSTANCE = new CrashHandler(); // 程序的Context對象 private Context mContext; // 用來存儲設備信息和異常信息 private Mapinfos = new HashMap (); // 用於格式化日期,作為日志文件名的一部分 private DateFormat formatter = new SimpleDateFormat(yyyy-MM-dd-HH-mm-ss); /** 保證只有一個CrashHandler實例 */ private CrashHandler() { } /** 獲取CrashHandler實例 ,單例模式 */ public static CrashHandler getInstance() { return INSTANCE; } /** * 初始化 * * @param context */ public void init(Context context) { mContext = context; // 獲取系統默認的UncaughtException處理器 mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); // 設置該CrashHandler為程序的默認處理器 Thread.setDefaultUncaughtExceptionHandler(this); } /** * 當UncaughtException發生時會轉入該函數來處理 */ @Override public void uncaughtException(Thread thread, Throwable ex) { if (!handleException(ex) && mDefaultHandler != null) { // 如果用戶沒有處理則讓系統默認的異常處理器來處理 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); } } /** * 自定義錯誤處理,收集錯誤信息 發送錯誤報告等操作均在此完成. * * @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, 很抱歉,程序出現異常,即將退出., Toast.LENGTH_LONG) .show(); Looper.loop(); } }.start(); // 收集設備參數信息 collectDeviceInfo(mContext); // 保存日志文件 saveCrashInfo2File(ex); return true; } /** * 收集設備參數信息 * * @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); } } } /** * 保存錯誤信息到文件中 * * @param ex * @return 返回文件名稱,便於將文件傳送到服務器 */ private String saveCrashInfo2File(Throwable ex) { StringBuffer sb = new StringBuffer(); for (Map.Entry entry : infos.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key + = + value + ); } 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; } }
完成了這個CrashHandler類之後,還需要自定義一個全局Application來啟動管理異常收集,以下是自定義的Application類,很簡單:
package com.example.crash; import android.app.Application; public class CrashApplication extends Application { @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(getApplicationContext()); } }最後,為了讓程序在啟動時使用我們自定義的Application,必須在Mainifest.xml的Application節點上,聲明出我們自定義的Application:
.....
運行以下程序:
在SD卡中找到crash文件夾,打開文件夾:
到處這個log日志,用notepad打開,查看內容如下:
TIME=1385535270000 ...... java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.crash/com.example.crash.MainActivity}: java.lang.ArithmeticException: divide by zero ...... Caused by: java.lang.ArithmeticException: divide by zero at com.example.crash.MainActivity.onCreate(MainActivity.java:13) at android.app.Activity.performCreate(Activity.java:5243) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140) ... 11 more java.lang.ArithmeticException: divide by zero at com.example.crash.MainActivity.onCreate(MainActivity.java:13) ......好了,程序中未捕獲的異常被及時捕捉到,保存在SD卡中,並且給用戶良好的提示信息,被沒有一下子crash掉,通過SD卡中的錯誤日志,我們可以很快定義到錯誤的根源,方便我們及時對程序進行修正。當然了,這裡我由於做的是個Demo,所以相關錯誤日志僅僅保存在了SD卡上,其實好的做法是將錯誤日志上傳到服務器中,以便我們收集來自四面八方用戶的日志,為程序進行更新迭代升級。
注:該文是我學習筆記,裡面會有一些Bug。程序僅作為參考實例,不能直接使用到真實項目中,請諒解!
參考資料:http://www.cjsdn.net/Doc/JDK60/java/lang/Thread.UncaughtExceptionHandler.html
http://www.cnblogs.com/draem0507/archive/2013/05/25/3099461.html
http://blog.csdn.net/liuhe688/article/details/6584143
有時候關閉了手機qq還是能收到信息,手機qq如何完全退出呢?下面我們就一起來看看吧! 手機QQ推出登陸教程方法一、退出QQ程序 第一步:打開手機QQ 第二步
Android Studio 1.0正式版發布啦今天是個大日子,Android Studio 1.0 終於發布了正式版, 這對於Android開發者來說簡直是喜大普奔的大
研究了一下android的apk的簽名和代碼的混淆打包,如果不混淆打包,那麼apk可以直接被人反編譯出來查看源碼,混淆打包雖然還是能看懂,但是沒有那麼好懂了,至少要話費些
vivo x7怎麼截圖?vivo x7手機是剛剛發布出來的新機,可能有用戶還不會截屏,下文介紹vivo x7截屏圖文流程,一起來瞧瞧吧! vivo x7截