Android 應用程序異常信息捕獲發送至服務器

對於已經上線的項目,為瞭避免有沒有考慮到的bug發生,方便維護以及為瞭有更好的用戶體驗,不能再發生異常的時候彈出系統默認的提示框. 而是應該由自己程序本身捕獲,先保存到本地,當下次打開軟件時上傳到服務器. 這樣也可以為我們收集到BUG的第一手資料(主動收集), 更快地定位到異常位置並修復,這樣既節省瞭時間,也提高瞭工作的效率.

還有本身這種收集異常信息的做法也是借鑒於windows中殺毒軟件的做法(如遇到本地病毒庫或者服務器上都沒有遇到的病毒,上傳添加到病毒庫中) ..或者有很多軟件做的用戶反饋,模式都比較相似

 

 

這裡還有一些細節要註意:

[java]
1. 在上傳的時候還可以將該<SPAN style="COLOR: #cc0000">app的version(版本號),該手機的型號,網絡制式</SPAN>等信息一並發送的服務器  
2. 原因:<SPAN style="COLOR: #cc0000">Android的兼容性</SPAN>眾所周知,所以可能錯誤不是每個手機都會報錯,還是有<SPAN style="COLOR: #cc0000">針對性</SPAN>的去debug比較好,而不是全部都進行分析,花費不必要的時間:  
   某些BUG可能是由於屏幕適配,或者由於不同品牌的手機使用的並不是原生系統,一般是經過修改過的,比較典型的是小米手機,對於系統修改的部分非常多.,有可能在原生系統 
或者主流的三星,摩托等手機上運行無誤的程序,會發生特定的異常 

1. 在上傳的時候還可以將該app的version(版本號),該手機的型號,網絡制式等信息一並發送的服務器
2. 原因:Android的兼容性眾所周知,所以可能錯誤不是每個手機都會報錯,還是有針對性的去debug比較好,而不是全部都進行分析,花費不必要的時間:
   某些BUG可能是由於屏幕適配,或者由於不同品牌的手機使用的並不是原生系統,一般是經過修改過的,比較典型的是小米手機,對於系統修改的部分非常多.,有可能在原生系統
或者主流的三星,摩托等手機上運行無誤的程序,會發生特定的異常

 

 

原理: 跟JavaEE的自定義異常捕獲一樣,將錯誤一直向上拋,然後在最上層統一處理。這裡就可以獲得異常信息,先保存到本地,下一次運行的時候上傳到服務區. 當然這些可以由程序員自己根據實際情況具體處理,這裡隻是提供捕獲異常並進一步處理的一個方案. 主要用到的是自定義的CrashHandler(繼承自UncaughtExceptionHandler),具體代碼如下:

 

[java]
/** 
* @author Tian
* 在Application中統一捕獲異常,保存到文件中下次再打開時上傳 
*/  
 
public class CrashHandler implements UncaughtExceptionHandler {  
 
 
/** 是否開啟日志輸出,在Debug狀態下開啟, 
* 在Release狀態下關閉以提示程序性能 
* */  
 
public static final boolean DEBUG = true;  
 
 
/** 系統默認的UncaughtException處理類 */  
 
private Thread.UncaughtExceptionHandler mDefaultHandler;  
 
/** CrashHandler實例 */  
 
private static CrashHandler INSTANCE;  
 
 
/** 程序的Context對象 */  
 
// private Context mContext;   
 
/** 保證隻有一個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 { //如果自己處理瞭異常,則不會彈出錯誤對話框,則需要手動退出app   
try {  
Thread.sleep(3000);  
} catch (InterruptedException e) {  
}  
android.os.Process.killProcess(android.os.Process.myPid());  
System.exit(10);  
}  
 
}  
 
/** 
* 自定義錯誤處理,收集錯誤信息 
* 發送錯誤報告等操作均在此完成. 
* 開發者可以根據自己的情況來自定義異常處理邏輯 
* @return 
* true代表處理該異常,不再向上拋異常, 
* false代表不處理該異常(可以將該log信息存儲起來)然後交給上層(這裡就到瞭系統的異常處理)去處理, 
* 簡單來說就是true不會彈出那個錯誤提示框,false就會彈出 
*/  
 
private boolean handleException(final Throwable ex) {  
if (ex == null) {  
return false;  
}  
 
// final String msg = ex.getLocalizedMessage();   
final StackTraceElement[] stack = ex.getStackTrace();  
final String message = ex.getMessage();  
 
//使用Toast來顯示異常信息   
new Thread() {  
@Override  
public void run() {  
 
Looper.prepare();  
// Toast.makeText(mContext, "程序出錯啦:" + message, Toast.LENGTH_LONG).show();   
// 可以隻創建一個文件,以後全部往裡面append然後發送,這樣就會有重復的信息,個人不推薦   
 
String fileName = "crash-" + System.currentTimeMillis() + ".log";  
File file = new File(Environment.getExternalStorageDirectory(), fileName);  
try {  
FileOutputStream fos = new FileOutputStream(file,true);  
fos.write(message.getBytes());  
for (int i = 0; i < stack.length; i++) {  
fos.write(stack[i].toString().getBytes());  
}  
 
fos.flush();  
fos.close();  
} catch (Exception e) {  
}  
Looper.loop();  
}  
 
}.start();  
return false;  
}  
 
// TODO 使用HTTP Post 發送錯誤報告到服務器 這裡不再做詳細描述  
// private void postReport(File file) {   
 
// }   
 
}  

/**
* @author Tian
* 在Application中統一捕獲異常,保存到文件中下次再打開時上傳
*/

public class CrashHandler implements UncaughtExceptionHandler {

/** 是否開啟日志輸出,在Debug狀態下開啟,
* 在Release狀態下關閉以提示程序性能
* */

public static final boolean DEBUG = true;

/** 系統默認的UncaughtException處理類 */

private Thread.UncaughtExceptionHandler mDefaultHandler;

/** CrashHandler實例 */

private static CrashHandler INSTANCE;

/** 程序的Context對象 */

// private Context mContext;

/** 保證隻有一個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 { //如果自己處理瞭異常,則不會彈出錯誤對話框,則需要手動退出app
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}

}

/**
* 自定義錯誤處理,收集錯誤信息
* 發送錯誤報告等操作均在此完成.
* 開發者可以根據自己的情況來自定義異常處理邏輯
* @return
* true代表處理該異常,不再向上拋異常,
* false代表不處理該異常(可以將該log信息存儲起來)然後交給上層(這裡就到瞭系統的異常處理)去處理,
* 簡單來說就是true不會彈出那個錯誤提示框,false就會彈出
*/

private boolean handleException(final Throwable ex) {
if (ex == null) {
return false;
}

// final String msg = ex.getLocalizedMessage();
final StackTraceElement[] stack = ex.getStackTrace();
final String message = ex.getMessage();

//使用Toast來顯示異常信息
new Thread() {
@Override
public void run() {

Looper.prepare();
// Toast.makeText(mContext, "程序出錯啦:" + message, Toast.LENGTH_LONG).show();
// 可以隻創建一個文件,以後全部往裡面append然後發送,這樣就會有重復的信息,個人不推薦

String fileName = "crash-" + System.currentTimeMillis() + ".log";
File file = new File(Environment.getExternalStorageDirectory(), fileName);
try {
FileOutputStream fos = new FileOutputStream(file,true);
fos.write(message.getBytes());
for (int i = 0; i < stack.length; i++) {
fos.write(stack[i].toString().getBytes());
}

fos.flush();
fos.close();
} catch (Exception e) {
}
Looper.loop();
}

}.start();
return false;
}

// TODO 使用HTTP Post 發送錯誤報告到服務器 這裡不再做詳細描述
// private void postReport(File file) {

// }

}

 

發佈留言