Android全域性捕獲崩潰異常記錄日誌儲存至本地並定時刪除
阿新 • • 發佈:2019-02-18
MainActivity內的程式碼如下:
package com.example.mycrashtest; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.widget.TextView; public class MainActivity extends Activity { private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv.setText("我沒初始化"); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
在這裡,我沒對TextView進行初始化,直接給其進行設定文字,會造成執行時異常,沒有對此異常進行處理,系統會彈出停止執行並強制關閉。
三.Appcation與Thread.UncaughtExceptionHandler
Appcation:Application和Activity,Service一樣,是android框架的一個系統元件,當android程式啟動時系統會建立一個 application物件,用來儲存系統的一些資訊。通常我們是不需要指定一個Application的,這時系統會自動幫我們建立,如果需要建立自己 的Application,也很簡單建立一個類繼承 Application並在manifest的application標籤中進行註冊(只需要給Application標籤增加個name屬性把自己的 Application的名字定入即可)。android系統會為每個程式執行時建立一個Application類的物件且僅建立一個,所以Application可以說是單例 (singleton)模式的一個類.且application物件的生命週期是整個程式中最長的,它的生命週期就等於這個程式的生命週期。因為它是全域性 的單例的,所以在不同的Activity,Service中獲得的物件都是同一個物件。所以通過Application來進行一些,資料傳遞,資料共享 等,資料快取等操作。
Thread.UncaughtExceptionHandler:當某一執行緒因未捕獲的異常而即將終止時,Java 虛擬機器將使用
Thread.getUncaughtExceptionHandler()
查詢該執行緒以獲得其 UncaughtExceptionHandler 的執行緒,並呼叫處理程式的uncaughtException 方法,將執行緒和異常作為引數傳遞。如果某一執行緒沒有明確設定其UncaughtExceptionHandler,則將它的ThreadGroup 物件作為其UncaughtExceptionHandler。如果ThreadGroup
物件對處理異常沒有什麼特殊要求,那麼它可以將呼叫轉發給預設的未捕獲異常處理程式。我們需要實現此介面,並註冊為程式中預設未捕獲異常處理。這樣當未捕獲異常發生時,就可以做一些個性化的異常處理操作。
四.程式碼實現
<pre name="code" class="java">package com.example.mycrashtest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
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.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
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.os.SystemClock;
import android.util.Log;
import android.widget.Toast;
/**
* <h3>全域性捕獲異常</h3>
* <br>
* 當程式發生Uncaught異常的時候,有該類來接管程式,並記錄錯誤日誌
*
*/
@SuppressLint("SimpleDateFormat")
public class CrashHandler implements UncaughtExceptionHandler {
public static String TAG = "MyCrash";
// 系統預設的UncaughtException處理類
private Thread.UncaughtExceptionHandler mDefaultHandler;
private static CrashHandler instance = new CrashHandler();
private Context mContext;
// 用來儲存裝置資訊和異常資訊
private Map<String, String> infos = new HashMap<String, String>();
// 用於格式化日期,作為日誌檔名的一部分
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
/** 保證只有一個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);
autoClear(5);
}
/**
* 當UncaughtException發生時會轉入該函式來處理
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
// 如果使用者沒有處理則讓系統預設的異常處理器來處理
mDefaultHandler.uncaughtException(thread, ex);
} else {
SystemClock.sleep(3000);
// 退出程式
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;
try {
// 使用Toast來顯示異常資訊
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(mContext, "很抱歉,程式出現異常,即將重啟.",
Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
// 收集裝置引數資訊
collectDeviceInfo(mContext);
// 儲存日誌檔案
saveCrashInfoFile(ex);
SystemClock.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
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 + "";
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());
} catch (Exception e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
}
/**
* 儲存錯誤資訊到檔案中
* @param ex
* @return 返回檔名稱,便於將檔案傳送到伺服器
* @throws Exception
*/
private String saveCrashInfoFile(Throwable ex) throws Exception {
StringBuffer sb = new StringBuffer();
try {
SimpleDateFormat sDateFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
String date = sDateFormat.format(new java.util.Date());
sb.append("\r\n" + date + "\n");
for (Map.Entry<String, String> entry : infos.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value + "\n");
}
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.flush();
printWriter.close();
String result = writer.toString();
sb.append(result);
String fileName = writeFile(sb.toString());
return fileName;
} catch (Exception e) {
Log.e(TAG, "an error occured while writing file...", e);
sb.append("an error occured while writing file...\r\n");
writeFile(sb.toString());
}
return null;
}
private String writeFile(String sb) throws Exception {
String time = formatter.format(new Date());
String fileName = "crash-" + time + ".log";
if (FileUtil.hasSdcard()) {
String path = getGlobalpath();
File dir = new File(path);
if (!dir.exists())
dir.mkdirs();
FileOutputStream fos = new FileOutputStream(path + fileName, true);
fos.write(sb.getBytes());
fos.flush();
fos.close();
}
return fileName;
}
public static String getGlobalpath() {
return Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "crash" + File.separator;
}
public static void setTag(String tag) {
TAG = tag;
}
/**
* 檔案刪除
* @param day 檔案儲存天數
*/
public void autoClear(final int autoClearDay) {
FileUtil.delete(getGlobalpath(), new FilenameFilter() {
@Override
public boolean accept(File file, String filename) {
String s = FileUtil.getFileNameWithoutExtension(filename);
int day = autoClearDay < 0 ? autoClearDay : -1 * autoClearDay;
String date = "crash-" + DateUtil.getOtherDay(day);
return date.compareTo(s) >= 0;
}
});
}
}
需要在自己的Appcation類中初始化這個CrashHandler:
package com.example.mycrashtest;
import android.app.Application;
public class MyAppcation extends Application{
@Override
public void onCreate() {
super.onCreate();
CrashHandler.getInstance().init(this);
}
}
在AndroidManifest.xml檔案中需要修改為自己的Appcation,並且加入操作SDCARD許可權(寫日誌):四.執行結果
系統會自動在SDCARD下建立crash目錄:開啟該檔案:
至此,整個專案梳理完畢,關於將日誌檔案上傳FTP,後面會另外講述,部分知識本人也處於學習階段,不足之處望大家諒解並提出,共同學習,謝謝!
專案下載:點選開啟連結