Android Crash 全局捕獲
首先應(yīng)該明白的一點是,Android在崩潰后會重新啟動崩潰時的那個Activity,如果你的Activity在初始化的時候就直接崩潰,那么你將連續(xù)得到 Crash 崩潰日志.這個說出來可能沒什么,可憐的我在看到崩潰日志時活脫脫的以為
uncaughtException(Thread thread, Throwable ex)方法被調(diào)用了兩次.知道原因后就很好解決,在崩潰時把Activity棧清空就可以.
1. UncaughtExceptionHandler
這是一個接口,只有一個方法 uncaughtException(Thread t, Throwable e) 而且注釋已經(jīng)說明,這個方法是虛擬機(JVM)去調(diào)用.所以我們編寫一個類實現(xiàn)此接口就可以了.
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
2. 異常時的常規(guī)處理
1. 顯示崩潰前的
Toast
2. 保存崩潰日志信息到sd卡
3. 重啟APP
實際項目中,我們需要做好這三件事.
/**
* 異常發(fā)生時,系統(tǒng)回調(diào)的函數(shù),我們在這里處理一些操作
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
showCrashToast();//展示崩潰前的Toast
saveCrashReport2SD(mContext, ex);//保存崩潰信息到sdcard
restartApp();//重新啟動APP
}
3. 完整的代碼
需要注意的地方寫在注釋里了.
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private Context mContext;
private static CrashHandler mInstance = new CrashHandler();
private CrashHandler() {
}
/**
* 單例模式,保證只有一個CrashHandler實例存在
*
* @return
*/
public static CrashHandler getInstance() {
return mInstance;
}
/**
* 異常發(fā)生時,系統(tǒng)回調(diào)的函數(shù),我們在這里處理一些操作
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
showCrashToast();
saveCrashReport2SD(mContext, ex);
restartApp();
}
/**
* 為我們的應(yīng)用程序設(shè)置自定義Crash處理
*/
public void initCrashHandler(Context context) {
mContext = context;
Thread.setDefaultUncaughtExceptionHandler(this);
}
private void restartApp() {
Intent intent = new Intent(App.getInstance().getApplicationContext(),
InitAdvsMultiActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent restartIntent = PendingIntent.getActivity(App.getInstance()
.getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT);
//重啟應(yīng)用
AlarmManager mgr = (AlarmManager) App.getInstance().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis(), restartIntent);
//清空Activity棧,防止系統(tǒng)自動重啟至崩潰頁面,導(dǎo)致崩潰再次出現(xiàn).
ActivityLifeManager.getInstance().finishAllActivity();
//退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
System.gc();
}
private void showCrashToast() {
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(App.getInstance().getApplicationContext(), "很抱歉,程序出現(xiàn)異常,即將重啟",
Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
try {
Thread.sleep(1000);//Toast展示的時間
} catch (InterruptedException e) {
}
}
/**
* 獲取一些簡單的信息,軟件版本,手機版本,型號等信息存放在LinkedHashMap中
*
* @param context
* @return
*/
private HashMap<String, String> obtainSimpleInfo(Context context) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
PackageManager mPackageManager = context.getPackageManager();
PackageInfo mPackageInfo = null;
try {
mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(),
PackageManager.GET_ACTIVITIES);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
map.put("APP", "AppName");
map.put("用戶名", SharedPreferencesUtils.getInstance().getUserName());
map.put("品牌", "" + Build.BRAND);
map.put("型號", "" + Build.MODEL);
map.put("SDK版本", "" + Build.VERSION.SDK_INT);
map.put("versionName", mPackageInfo.versionName);
map.put("versionCode", "" + mPackageInfo.versionCode);
map.put("crash時間", parserTime(System.currentTimeMillis()));
return map;
}
/**
* 獲取系統(tǒng)未捕捉的錯誤信息
*
* @param throwable
* @return
*/
private String obtainExceptionInfo(Throwable throwable) {
StringWriter mStringWriter = new StringWriter();
PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
throwable.printStackTrace(mPrintWriter);
mPrintWriter.close();
Log.e(TAG, mStringWriter.toString());
return mStringWriter.toString();
}
/**
* 保存獲取的 軟件信息,設(shè)備信息和出錯信息保存在SDcard中
*
* @param context
* @param ex
* @return
*/
private String saveCrashReport2SD(Context context, Throwable ex) {
String fileName = null;
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key).append(" = ").append(value).append("\n");
}
sb.append(obtainExceptionInfo(ex));
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dir = new File(CrashConstant.CRASH_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
try {
fileName = dir.toString() + File.separator + parserTime(System.currentTimeMillis()) +".txt";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(sb.toString().getBytes());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return fileName;
}
/**
* 將毫秒數(shù)轉(zhuǎn)換成yyyy-MM-dd-HH-mm-ss的格式,并在后綴加入隨機數(shù)
*
* @param milliseconds
* @return
*/
private String parserTime(long milliseconds) {
System.setProperty("user.timezone", "Asia/Shanghai");
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone.setDefault(tz);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
return format.format(new Date(milliseconds));
}
}