我的博客原文地址
GitHub源碼地址
Android應用開發中不可避免地會發生崩潰,特別是在用戶使用過程中,一些特定場景的偶然概率的crash會通常讓開發者抓狂。幸運的是Android提供了處理這類問題的方法,當App Crash時,我們可以記錄下Crash的原因或者是一些設備信息,並上傳到服務器供開發者分析,以便開發者迅速定位問題原因。
實現這個功能我們需要實現Thread.UncaughtExceptionHandler
這個接口。裏面只有一個方法uncaughtException
。當我們註冊一個UncaughtExceptionHandler
之後,當我們的程序crash時就會回調uncaughtException
方法,在uncaughtException
方法中就可以獲取到異常信息,我們可以選擇把異常信息存儲到SD卡中,然後在合適的時機上傳到服務器。或者我們還可以在crash發生時,彈出一個對話框告訴用戶程序crash了,然後再退出,這樣會比閃退溫和一些。
下面我們實現了一個捕獲OOM的UncaughtExceptionHandler
的類,發生OOM時可以抓取一些日誌,然後可以通過MAT分析生成的hprof文件來定位問題。下面來看一下這個類的用法:
CrashHandler類
public class OomCrashHandler implements Thread.UncaughtExceptionHandler {
final String TAG = "OomCrashHandler";
private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
private String mPackageName;
private int mPid;
private StringBuilder mFilename = new StringBuilder(120);
private final String OOM_TARGET_FOLDER = "/crash_log/";
private static final long ONE_DAY_TIME_IN_MILLISECONDS = 24*60*60*1000;
private boolean mDumpingHprof;
OomCrashHandler(String pkgName, int pid) {
this.mPackageName = pkgName;
this.mPid = pid;
this.mDumpingHprof = false;
}
public void uncaughtException(Thread thread, Throwable throwable) {
if(throwable instanceof OutOfMemoryError && !mDumpingHprof) {
Log.w(TAG, "OomCrashHandler capture a oom exception !!!");
if(!dumpHprofData()) {
Log.e(TAG, "Aborting ...");
}else{
//uploadExceptionToServer(); // TODO
}
}
if(mDefaultUncaughtExceptionHandler != null){
mDefaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
} else {
Process.killProcess(Process.myPid());
}
}
//獲取目錄大小,單位 M
private double getDirSize(File file) {
if(!file.exists()) {
Log.w(TAG, file.toString() + " may not exists !");
return 0.0D;
} else if(!file.isDirectory()) {
double size = (double)file.length() / 1024.0D / 1024.0D;
return size;
} else {
File[] files = file.listFiles();
double size = 0.0D;
int length = files.length;
for(int i = 0; i < length; ++i) {
File f = files[i];
size += this.getDirSize(f);
}
return size;
}
}
//當目錄大於1G時且創建時間大於1天時刪除舊文件
private void deleteOldFiles(File file) {
if(getDirSize(file) >= 1024.0D) {
Log.w(TAG, "begin to delete old files !");
long currentTimeMillis = System.currentTimeMillis();
File[] listFiles = file.listFiles();
for(int i = 0; i < listFiles.length; ++i) {
if(listFiles[i].isFile()) {
if(currentTimeMillis - listFiles[i].lastModified() > ONE_DAY_TIME_IN_MILLISECONDS) {
listFiles[i].delete();
}
} else if(listFiles[i].isDirectory()) {
deleteOldFiles(listFiles[i]);
}
}
}
}
private boolean getDumpDestinationOfHeap() {
mFilename.delete(0, mFilename.length());
mFilename.append(Environment.getExternalStorageDirectory());
mFilename.append(OOM_TARGET_FOLDER);
File target = new File(mFilename.toString());
if (!target.exists()) {
if (!target.mkdirs()) {
Log.e(TAG, "Creating target hprof directory: \"" + mFilename.toString() + "\" was failed!");
return false;
}
}
deleteOldFiles(target);
mFilename.append(mPackageName);
mFilename.append("_PID:" + mPid);
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
mFilename.append("_TIME:"+time);
mFilename.append(".hprof");
target = new File(mFilename.toString().trim());
try {
target.createNewFile();
} catch (IOException e) {
Log.e(TAG, "Creating target hprof file: \"" + mFilename.toString() + "\" was failed! Reason:" + e);
return false;
}
return true;
}
private boolean dumpHprofData() {
if (!getDumpDestinationOfHeap()) {
return false;
}
Log.w(TAG, "Begin to dump hprof to " + mFilename.toString());
long beginDumpTime = SystemClock.uptimeMillis();
try {
mDumpingHprof = true;
Debug.dumpHprofData(mFilename.toString());
} catch (IOException e) {
Log.e(TAG, "Dump hprof to " + mFilename.toString() + " failed ! " + e);
return false;
}
long endDumpTime = (SystemClock.uptimeMillis() - beginDumpTime) / 1000;
Log.w(TAG, "Dump succeed!, Took " + endDumpTime + "s");
return true;
}
public static void registerExceptionHandler(Context context) {
OomCrashHandler handler = new OomCrashHandler(context.getPackageName(), android.os.Process.myPid());
Thread.setDefaultUncaughtExceptionHandler(handler);
}
}
調用
private void createOOM(){
OomCrashHandler.registerExceptionHandler(this);
ArrayList<Bitmap> arrayList = new ArrayList<>();
for(int i = 0; i<100000; i++){
arrayList.add(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
}
}