編輯:關於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 Map infos = 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推出登陸教程方法一、退出QQ程序 第一步:打開手機QQ 第二步
Android Studio使用教程(三):常用快捷鍵
Android Studio 1.0正式版發布啦今天是個大日子,Android Studio 1.0 終於發布了正式版, 這對於Android開發者來說簡直是喜大普奔的大
android代碼簽名和混淆打包
研究了一下android的apk的簽名和代碼的混淆打包,如果不混淆打包,那麼apk可以直接被人反編譯出來查看源碼,混淆打包雖然還是能看懂,但是沒有那麼好懂了,至少要話費些
vivo x7怎麼截圖 vivo x7截屏教程
vivo x7怎麼截圖?vivo x7手機是剛剛發布出來的新機,可能有用戶還不會截屏,下文介紹vivo x7截屏圖文流程,一起來瞧瞧吧! vivo x7截