UncaughtExceptionHandler使用

使用場景

  • 同步數據庫時,有些特定的情況導致線程停止,但數據庫數據還沒有同步完,可以再重新打開線程,再同步完剩下的數據。
  • 移動端安卓客戶端,不想接入如Bugly或者啄木鳥這種功能完善的第三方SDK,而只是想普通記錄應用奔潰時候的日誌。
  • 其他種種情況

測試人員告訴我,有一些難以復現的bug,可不可以加個日誌記錄,那好吧,反正也是小項目,不接第三方,自己寫好了。

思路

我準備在工程類Application類里加上異常處理器,如果出現造成閃退的異常,就獲取異常,寫入一個txt文件內,並存到本地。(如果外存儲可寫,就存到 手機存儲/項目名/cache目錄下。不可寫就存到/data/data/包名/files/cache目錄下)詳情見下文的文件存儲工具類。

1、自定義一個處理器,實現接口Thread.UncaughtExceptionHandler

/**
 * @author haizhuo
 * @introduction 崩潰異常處理器
 */
public class MyCrashExceptionHandler implements Thread.UncaughtExceptionHandler {

    private static MyCrashExceptionHandler instance;

    private Context mContext;

    private MyCrashExceptionHandler() {

    }

    public static MyCrashExceptionHandler getInstance() {
        if (instance == null) {
            synchronized (MyCrashExceptionHandler.class) {
                if (instance == null) {
                    instance = new MyCrashExceptionHandler();
                }
            }
        }
        return instance;
    }

    public void init(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("Context is null!!!");
        }
        mContext = context.getApplicationContext();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (e == null) {
            //讓系統默認的異常處理器來處理
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
        } else {
            new Thread(() -> {
                Looper.prepare();
                Toast.makeText(mContext, "程序發生異常,即將退出", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }).start();
            printCrashInfo(e);
            SystemClock.sleep(2000);
            //清棧的原因是,有時候出現崩潰會重啓app,造成不能正常退出,體驗不好
            AppManager.getInstance().AppExit();
            //android.os.Process.killProcess(android.os.Process.myPid());
            //System.exit(0);
        }
    }

    public void printCrashInfo(Throwable ex) {
        String time = TimeUtil.getCurrentTime();
        File crashFile = new File(StorageUtil.getCacheDir(), time + ".txt");
        try {
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile)));
            pw.println(time);
            PackageManager pm = mContext.getPackageManager();
            PackageInfo packageInfo = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
            pw.println("packageName:" + mContext.getPackageName());
            pw.println("APK Ver:" + packageInfo.versionName + " _ " + packageInfo.versionCode);
            pw.println("---------------------------------------------------");
            ex.printStackTrace(pw);
            pw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、在Application的onCreate方法中初始化

/**
 * @author haizhuo
 */
public class MyApplication extends Application{
    private static MyApplication myApplication;
    /**
     * 崩潰異常處理器
     */
    private MyCrashExceptionHandler myCrashExceptionHandler;

    /**
     * 獲取實例
     *
     * @return myApplication
     */
    public static MyApplication getInstance() {
        return myApplication;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        myApplication = this;
        myCrashExceptionHandler=MyCrashExceptionHandler.getInstance();
        myCrashExceptionHandler.init(this);
    }
    
}

堆棧管理工具類

/**
 * @author haizhuo
 * @description activity堆棧式管理
 */
public class AppManager {

    private final String TAG="AppManager";
    private Stack<Activity> activityStack;
    private static AppManager mInstance;

    private AppManager() {
        activityStack= new Stack<>();
    }

    /**
     * 單一實例
     */
    public static AppManager getInstance() {
        if (mInstance == null) {
            mInstance = new AppManager();
        }
        return mInstance;
    }

    /**
     * 添加Activity到堆棧
     */
    public void addActivity(Activity activity) {
        if (activityStack == null) {
            activityStack = new Stack<>();
        }
        activityStack.add(activity);
    }

    /**
     * 從堆棧中刪除Activity
     */
    public void removeActivity(Activity activity){
        if (activity == null || activityStack.isEmpty()) {
            return;
        }
        activityStack.remove(activity);
    }

    /**
     * 獲取當前Activity(堆棧中最後一個壓入的)
     */
    public Activity currentActivity() {
        Activity activity = null;
        if (!activityStack.empty()) {
            activity = activityStack.lastElement();
        }
        return activity;
    }

    /**
     * 結束當前Activity(堆棧中最後一個壓入的)
     */
    public void finishActivity() {
        Activity activity = activityStack.lastElement();
        finishActivity(activity);
    }

    /**
     * 結束指定的Activity
     */
    public void finishActivity(Activity activity) {
        if (activity == null || activityStack.isEmpty()) {
            return;
        }
        if (!activity.isFinishing()) {
            activity.finish();
        }
        activityStack.remove(activity);
    }



    /**
     * 結束指定類名的Activity
     */
    public void finishActivity(Class<?> cls) {
        for (Activity activity : activityStack) {
            if (activity.getClass().equals(cls)) {
                finishActivity(activity);
                break;
            }
        }
    }

    /**
     * 結束所有Activity
     */
    public void finishAllActivity() {
        try {
            while (true) {
                Activity activity = currentActivity();
                if (activity == null) {
                    break;
                }
                finishActivity(activity);
            }
        } catch (Exception e) {
            Logger.log(Logger.ERROR,TAG,"關閉所有Activity錯誤"+e.getMessage(),null);
        }finally {
            activityStack.clear();
        }
    }

    /**
     * 獲取指定的Activity
     *
     */
    public Activity getActivity(Class<?> cls) {
        if (activityStack != null){
            for (Activity activity : activityStack) {
                if (activity.getClass().equals(cls)) {
                    return activity;
                }
            }
        }
        return null;
    }

    /**
     * 退出應用程序
     */
    public void AppExit() {
        try {
            finishAllActivity();
            // 殺死該應用進程
            /*android.os.Process.killProcess(android.os.Process.myPid());*/
            //終止當前JVM虛擬機,效果和上面的殺死進程差不多
            /*System.exit(0);*/
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

存儲文件工具類

/**
 * @author haizhuo
 * @introduction 工具類,存儲文件
 */
public class StorageUtil {
    private StorageUtil() {
    }

    /**
     * 判斷外存儲是否可寫
     *
     * @return
     */
    public static boolean isExternalStorageWritable() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    private static File getAppDir() {
        File rootDir;
        if (isExternalStorageWritable()) {
            rootDir = new File(Environment.getExternalStorageDirectory(),MyApplication.getInstance().getAppName());
        } else {
            rootDir = MyApplication.getInstance().getFilesDir();
        }
        if (!rootDir.exists()) {
            rootDir.mkdirs();
        }
        return rootDir;
    }

    /**
     * 獲取當前app文件存儲目錄
     *
     * @return
     */
    public static File getFileDir() {
        File fileDir = new File(getAppDir(), "file");
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        return fileDir;
    }


    /**
     * 獲取當前app圖片文件存儲目錄
     *
     * @return
     */
    public static File getImageDir() {
        File imageDir = new File(getAppDir(), "image");
        if (!imageDir.exists()) {
            imageDir.mkdirs();
        }
        return imageDir;
    }

    /**
     * 獲取當前app緩存文件存儲目錄
     *
     * @return
     */
    public static File getCacheDir() {
        File cacheDir = new File(getAppDir(), "cache");
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
        return cacheDir;
    }

    /**
     * 獲取當前app音頻文件存儲目錄
     *
     * @return
     */
    public static File getAudioDir() {
        File audioDir = new File(getAppDir(), "audio");
        if (!audioDir.exists()) {
            audioDir.mkdirs();
        }
        return audioDir;
    }

    /**
     * @param context
     * @return "/storage/emulated/0/Android/data/com.xxx.xxx/cache"目錄
     */
    public static String getExternalCacheDir(Context context) {
        return context.getExternalCacheDir().getAbsolutePath();
    }

    /**
     * 創建一個文件夾, 存在則返回, 不存在則新建
     *
     * @param parentDirectory 父目錄路徑
     * @param directory       目錄名
     * @return 文件,null代表失敗
     */
    public static File getDirectory(String parentDirectory, String directory) {
        if (TextUtils.isEmpty(parentDirectory) || TextUtils.isEmpty(directory)) {
            return null;
        }
        File file = new File(parentDirectory, directory);
        boolean flag;
        if (!file.exists()) {
            flag = file.mkdir();
        } else {
            flag = true;
        }
        return flag ? file : null;
    }

    /**
     * 根據輸入流,保存文件
     * 類型:直接覆蓋文件
     *
     * @param file
     * @param is
     * @return
     */
    public static boolean writeFile(File file, InputStream is) {
        OutputStream os = null;
        try {
            //在每次調用的時候都會覆蓋掉原來的數據
            os = new FileOutputStream(file);
            byte data[] = new byte[1024];
            int length = -1;
            while ((length = is.read(data)) != -1) {
                os.write(data, 0, length);
            }
            os.flush();
            return true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Logger.e(new RuntimeException("FileNotFoundException occurred. ", e), "發生錯誤", "");
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            Logger.e(new RuntimeException("IOException occurred. ", e), "發生錯誤", "");
            return false;
        } finally {
            closeStream(os);
            closeStream(is);
        }
    }

    /**
     * 刪除文件或文件夾
     *
     * @param file
     */
    public static void deleteFile(File file) {
        try {
            if (file == null || !file.exists()) {
                return;
            }

            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null && files.length > 0) {
                    for (File f : files) {
                        if (f.exists()) {
                            if (f.isDirectory()) {
                                deleteFile(f);
                            } else {
                                f.deleteOnExit();
                                Logger.d("StorageUtil", "刪除文件 " + f.getAbsolutePath());
                            }
                        }
                    }
                }
            } else {
                file.deleteOnExit();
                Logger.d("StorageUtil", "刪除文件 " + file.getAbsolutePath());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存文件
     *
     * @param inputStream  輸入流,比如獲取網絡下載的字節流 ResponseBody.byteStream()
     * @param outputStream 輸出流,比如FileOutputStream則是保存文件
     * @return
     */
    public static boolean saveFile(InputStream inputStream, OutputStream outputStream) {
        if (inputStream == null || outputStream == null) {
            return false;
        }
        try {
            try {
                byte[] buffer = new byte[1024 * 4];
                while (true) {
                    int read = inputStream.read(buffer);
                    if (read == -1) {
                        break;
                    }
                    outputStream.write(buffer, 0, read);
                }
                outputStream.flush();
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            } finally {
                inputStream.close();
                outputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 關閉流
     *
     * @param closeable
     */
    public static void closeStream(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                throw new RuntimeException("關閉流失敗!", e);
            }
        }
    }


    /**
     * 通過uri拿到文件真實路徑
     * @param context
     * @param uri
     * @return
     */
    public static String getRealFilePath(final Context context, final Uri uri) {
        if (null == uri) {return null;}
        final String scheme = uri.getScheme();
        String data = null;
        if (scheme == null)
        {data = uri.getPath();}
        else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
            data = uri.getPath();
        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
            final Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
            if (null != cursor) {
                if (cursor.moveToFirst()) {
                    final int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
                    if (index > -1) {
                        data = cursor.getString(index);
                    }
                }
                cursor.close();
            }
        }
        return data;
    }
}

發佈了28 篇原創文章 · 獲贊 17 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章