使用CrashHandler來獲取應用的oom crash信息

我的博客原文地址
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));
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章