android 處理常式全域異常和錯誤

來源:互聯網
上載者:User

本文將分析在程式出錯的情況下如何收集相關的錯誤資訊,並發送錯誤資訊到伺服器供開發人員分析和偵錯工具。錯誤資訊將成為您Debug的一把利刃,通過錯誤資訊您可以最及時的掌握程式在各個系統版本和裝置上的運行情況。

錯誤處理介紹

在一般情況下,OPhone程式出錯都會出現一個提示對話方塊
這種情況下,使用者只有點擊“強行關閉”來結束程式。當該對話方塊出現對使用者來說是相當不友好的,本文中將會告訴您如何在程式出錯時不顯示該對話方塊。
隨著OPhone裝置和系統版本的增加,現在在不同裝置和版本上偵錯工具越來越麻煩,開發人員不可能購買所有的裝置來逐個偵錯工具。如果程式在模擬器上運行正常但是到終端使用者手中運行卻出現了錯誤,這種情況下如果可以收集到程式錯誤堆棧資訊和具體裝置的資訊,對開發人員偵錯工具就有莫大的協助了。
要收集錯誤資訊,我們需要瞭解兩個主要介面API的使用:Android.app.Application 和java.lang.Thread.UncaughtExceptionHandler 。下面就對著兩個API做簡單介紹。

UncaughtExceptionHandler:線程未捕獲異常控制器是用來處理未捕獲異常的。如果程式出現了未捕獲異常預設情況下則會出現上面所示的強行關閉對話方塊。在本文將實現該介面並註冊為程式中的預設未捕獲異常處理。這樣當未捕獲異常發生時,就可以做些異常處理操作,例如:收集異常資訊,發送錯誤報表 等。

Application:在開發OPhone應用時都會和Activity打交道,而Application使用的就相對較少了。Application 在OPhone中是用來管理應用程式的全域狀態的,比如載入資源檔。在應用程式啟動的時候Application會首先建立,然後才會根據情況(Intent)來啟動相應的Activity或者Service。在本文將在Application中註冊未捕獲異常處理器。

 

UncaughtExceptionHandler介面實現

首先建立一個OPhone項目(項目的建立請參考OPhoneSDN上的其他文章),本文樣本項目名稱為:CrashReporter ;包名為:org.goodev.cr;並建立一個預設的Activity名字為:ReporterTest。然後建立CrashHandler類實現UncaughtExceptionHandler介面,並實現其函數:public void uncaughtException(Thread thread, Throwable ex)。CrashHandler類實現了錯誤報表的主要處理邏輯,該類代碼如下(在代碼中會有詳細注釋來解釋各種處理情況):

package org.goodev.cr;

Import 省略...;
/**
* UncaughtException處理類,當程式發生Uncaught異常的時候,有該類
* 來接管程式,並記錄 發送錯誤報表.
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
/** Debug Log tag*/
public static final String TAG = "CrashHandler";
/** 是否開啟日誌輸出,在Debug狀態下開啟,
* 在Release狀態下關閉以提示程式效能
* */
public static final boolean DEBUG = true;
/** 系統預設的UncaughtException處理類 */
private Thread.UncaughtExceptionHandler mDefaultHandler;
/** CrashHandler執行個體 */
private static CrashHandler INSTANCE;
/** 程式的Context對象 */
private Context mContext;

/** 使用Properties來儲存裝置的資訊和錯誤堆棧資訊*/
private Properties mDeviceCrashInfo = new Properties();
private static final String VERSION_NAME = "versionName";
private static final String VERSION_CODE = "versionCode";
private static final String STACK_TRACE = "STACK_TRACE";
/** 錯誤報表檔案的副檔名 */
private static final String CRASH_REPORTER_EXTENSION = ".cr";

/** 保證只有一個CrashHandler執行個體 */
private CrashHandler() {}
/** 擷取CrashHandler執行個體 ,單例模式*/
public static CrashHandler getInstance() {
if (INSTANCE == null) {
INSTANCE = new CrashHandler();
}
return INSTANCE;
}

/**
* 初始化,註冊Context對象,
* 擷取系統預設的UncaughtException處理器,
* 設定該CrashHandler為程式的預設處理器
*
* @param ctx
*/
public void init(Context ctx) {
mContext = ctx;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}

/**
* 當UncaughtException發生時會轉入該函數來處理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
//如果使用者沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
//Sleep一會後結束程式
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "Error : ", e);
}
Android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}

/**
* 自訂錯誤處理,收集錯誤資訊
* 發送錯誤報表等操作均在此完成.
* 開發人員可以根據自己的情況來自訂異常處理邏輯
* @param ex
* @return true:如果處理了該異常資訊;否則返回false
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
return true;
}
final String msg = ex.getLocalizedMessage();
//使用Toast來顯示異常資訊
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "程式出錯啦:" + msg, Toast.LENGTH_LONG)
.show();
Looper.loop();
}

}.start();
//收集裝置資訊
collectCrashDeviceInfo(mContext);
//儲存錯誤報表檔案
String crashFileName = saveCrashInfoToFile(ex);
//發送錯誤報表到伺服器
sendCrashReportsToServer(mContext);
return true;
}

/**
* 在程式啟動時候, 可以調用該函數來發送以前沒有發送的報告
*/
public void sendPreviousReportsToServer() {
sendCrashReportsToServer(mContext);
}

/**
* 把錯誤報表發送給伺服器,包含新產生的和以前沒發送的.
*
* @param ctx
*/
private void sendCrashReportsToServer(Context ctx) {
String[] crFiles = getCrashReportFiles(ctx);
if (crFiles != null && crFiles.length > 0) {
TreeSet<String> sortedFiles = new TreeSet<String>();
sortedFiles.addAll(Arrays.asList(crFiles));

for (String fileName : sortedFiles) {
File cr = new File(ctx.getFilesDir(), fileName);
postReport(cr);
cr.delete();// 刪除已發送的報告
}
}
}

private void postReport(File file) {
// TODO 使用HTTP Post 發送錯誤報表到伺服器
// 這裡不再詳述,開發人員可以根據OPhoneSDN上的其他網路操作
// 教程來提交錯誤報表
}

/**
* 擷取錯誤報表檔案名稱
* @param ctx
* @return
*/
private String[] getCrashReportFiles(Context ctx) {
File filesDir = ctx.getFilesDir();
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(CRASH_REPORTER_EXTENSION);
}
};
return filesDir.list(filter);
}
/**
* 儲存錯誤資訊到檔案中
* @param ex
* @return
*/
private String saveCrashInfoToFile(Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);

Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}

String result = info.toString();
printWriter.close();
mDeviceCrashInfo.put(STACK_TRACE, result);

try {
long timestamp = System.currentTimeMillis();
String fileName = "crash-" + timestamp + CRASH_REPORTER_EXTENSION;
FileOutputStream trace = mContext.openFileOutput(fileName,
Context.MODE_PRIVATE);
mDeviceCrashInfo.store(trace, "");
trace.flush();
trace.close();
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing report file...", e);
}
return null;
}

/**
* 收集程式崩潰的裝置資訊
*
* @param ctx
*/
public void collectCrashDeviceInfo(Context ctx) {
try {
PackageManager pm = ctx.getPackageManager();
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
PackageManager.GET_ACTIVITIES);
if (pi != null) {
mDeviceCrashInfo.put(VERSION_NAME,
pi.versionName == null ? "not set" : pi.versionName);
mDeviceCrashInfo.put(VERSION_CODE, pi.versionCode);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Error while collect package info", e);
}
//使用反射來收集裝置資訊.在Build類中包含各種裝置資訊,
//例如: 系統版本號碼,裝置生產商 等協助偵錯工具的有用資訊
//具體資訊請參考後面的
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
mDeviceCrashInfo.put(field.getName(), field.get(null));
if (DEBUG) {
Log.d(TAG, field.getName() + " : " + field.get(null));
}
} catch (Exception e) {
Log.e(TAG, "Error while collect crash info", e);
}

}

}

}

在上面CrashHandler實現中,當錯誤發生的時候使用Toast顯示錯誤資訊,然後收集錯誤報表並儲存在檔案中。 發送錯誤報表代碼請讀者自己實現。在uncaughtException函數中調用了Thread.sleep(3000);來讓線程停止一會是為了顯示Toast資訊給使用者,然後Kill程式。如果你不用Toast來顯示資訊則可以去除該代碼。除了Toast外,開發人員還可以選擇使用Notification來顯示錯誤內容並讓使用者選擇是否提交錯誤報表而不是自動認可。關於Notification的實現請讀者參考:NotificationManager。在發送錯誤判道的時候,可以先檢測網路是否可用,如果不可用則可以在以後網路情況可用的情況下發送。 網路監測代碼如下:
/**
* 檢測網路連接是否可用
* @param ctx
* @return true 可用; false 不可用
*/
private boolean isNetworkAvailable(Context ctx) {
ConnectivityManager cm =
(ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
if(cm == null) {
return false;
}
NetworkInfo[] netinfo = cm.getAllNetworkInfo();
if(netinfo == null) {
return false;
}
for (int i = 0; i < netinfo.length; i++) {
if(netinfo[i].isConnected()) {
return true;
}
}
return false;
}

 

Application 實現

實現一個自訂Application來註冊CrashHandler. 代碼如下:
public class CrashApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
CrashHandler crashHandler = CrashHandler.getInstance();
//註冊crashHandler
crashHandler.init(getApplicationContext());
//發送以前沒發送的報告(可選)
crashHandler.sendPreviousReportsToServer();
}

}

在AndroidManifest.xml中註冊

最後只要在AndroidManifest.xml中註冊CrashApplication就可以了。代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.android.com/apk/res/android"
package="org.goodev.cr"
Android:versionCode="1"
Android:versionName="1.0">
<application Android:icon="@drawable/icon" android:label="@string/app_name"
Android:name=".CrashApplication">
<activity Android:name=".ReporterTest"
Android:label="@string/app_name">
<intent-filter>
<action Android:name="android.intent.action.MAIN" />
<category Android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

 

原文地址:http://blog.csdn.net/zhong_ch/archive/2011/04/27/6366023.aspx

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.