Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android程序crash處理

Android程序crash處理

編輯:關於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:

 

 


      .....
    

配置SDCard寫文件的權限:

 

 

運行以下程序:

 

\

在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

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved