The CrashHandler that you need for a project

Last week, my new friend asked about the CrashHandler in the project. At that time, I just said that it was the weekend and I felt a sudden impulse to hand over a comprehensive CrashHandler, which would be helpful for the future project development and the improvement of the current project.


directory

  • Cognition and Function
  • The capture of the Crash
  • Obtaining Crash information
  • Crash logs were written and uploaded
  • use
  • Some attention
  • The last

Cognition and Function

CrashHandler: A Crash handler that captures and processes Crash information

  1. Test usage: Application In daily development, we often need to go to Logcat to test our App, but due to many reasons, Android Monitor will lose flash screen or Crash information. That’s when you need oneCrashHandlerTo write Crash locally so we can look at it anytime, anywhere.
  2. Online use: The crash rate of the application is an important criterion for users to evaluate and select the application, so we cannot borrow users’ phones to analyze the crash cause after the application is online. In order to reduce the collapse rate, this time needsCrashHandlerTo help us send crash information back to the background so we can fix it in time.

Let’s write a practical, localized, lightweight CrashHandler by hand.

The capture of the Crash

  1. implementationThread.UncaughtExceptionHandlerInterface, rewriteuncaughtExceptionMethod, at which point your CrashHandler is ready to accept and handle exceptions.
  2. callThread.setDefaultUncaughtExceptionHandler(CrashHandler)To use our customCrashHandlerTo replace the system defaultCrashHandler
  3. Combine the singleton pattern
  4. General three steps: catch exceptions, information data acquisition, data write and upload The general initialization code is as follows:

    private RCrashHandler(String dirPath) {
        mDirPath = dirPath;
        File mDirectory = new File(mDirPath);
        if(! mDirectory.exists()) { mDirectory.mkdirs(); } } publicstatic RCrashHandler getInstance(String dirPath) {
        if (INSTANCE == null) {
            synchronized (RCrashHandler.class) {
                if (INSTANCE == null) {
                    INSTANCE = newRCrashHandler(dirPath); }}}return INSTANCE;
    }

    /** * init ** @param context * @param crashUploader crash message upload interface callback */
    public void init(Context context, CrashUploader crashUploader) {
        mCrashUploader = crashUploader;
        mContext = context;
        // Save a copy of the system default CrashHandler
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // Replace the default exception handler with our custom exception handler
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /** * this is the most important function. If there is an uncaughtException in the program, the system will call the uncaughtException method automatically ** @param t
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if(! catchCrashException(e) && mDefaultHandler ! =null) {
            // The default exception handling method is called when there is no custom CrashHandler
            mDefaultHandler.uncaughtException(t, e);
        } else {
            // Exit the applicationkillProcess(); }}** @param ex * @return true: If the error message is processed, the error message will be sent. Otherwise return false. */
    private boolean catchCrashException(Throwable ex) {
        if (ex == null) {
            return false;
        }

        new Thread() {
            public void run() {
// Looper.prepare();
// toast.maketext (mContext, "sorry, program exception, about to exit ", 0).show();
// Looper.loop();
                Intent intent = new Intent();
                intent.setClass(mContext, CrashActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                ActivityCollector.finishAll();
                mContext.startActivity(intent);
            }
        }.start();
        // Collect device parameter information
        collectInfos(mContext, ex);
        // Save the log file
        saveCrashInfo2File();
        // Upload crash information
        uploadCrashMessage(infos);

        return true;
    }


  /** * Exit the application */
    public static void killProcess() {
        // End the application
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                ToastUtils.showLong("Oops, something's wrong with the program...");
                Looper.loop();
            }
        }).start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            RLog.e("CrashHandler.InterruptedException--->" + ex.toString());
        }
        // Exit the program
        Process.killProcess(Process.myPid());
        System.exit(1);
    }Copy the code

Obtaining Crash information

  • Obtaining exception Information
 /** * get the message that the exception was caught ** @param ex */
    private String collectExceptionInfos(Throwable ex) {
        Writer mWriter = new StringWriter();
        PrintWriter mPrintWriter = new PrintWriter(mWriter);
        ex.printStackTrace(mPrintWriter);
        ex.printStackTrace();
        Throwable mThrowable = ex.getCause();
        // The iteration stack queue writes all exception information to writer
        while(mThrowable ! =null) {
            mThrowable.printStackTrace(mPrintWriter);
            // Line breaks between each exception stack
            mPrintWriter.append("\r\n");
            mThrowable = mThrowable.getCause();
        }
        // Remember to close
        mPrintWriter.close();
        return mWriter.toString();
    }Copy the code
  • Obtaining Application Information
 /** * Obtain application package parameters */
    private void collectPackageInfos(Context context) {
        try {
            // Get the package manager
            PackageManager mPackageManager = context.getPackageManager();
            // Get information about the application, the main Activity
            PackageInfo mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            if(mPackageInfo ! =null) {
                String versionName = mPackageInfo.versionName == null ? "null" : mPackageInfo.versionName;
                String versionCode = mPackageInfo.versionCode + ""; mPackageInfos.put(VERSION_NAME, versionName); mPackageInfos.put(VERSION_CODE, versionCode); }}catch(PackageManager.NameNotFoundException e) { e.printStackTrace(); }}Copy the code
  • Obtain device hardware information (to locate bugs more effectively for users of different models)
/** * Extract device hardware and version information from system properties */
    private void collectBuildInfos() {
        // Reflection mechanism
        Field[] mFields = Build.class.getDeclaredFields();
        // Iteration Build field key-value the information here is mainly for the reasons of the various versions of the phone in the server side
        for (Field field : mFields) {
            try {
                field.setAccessible(true);
                mDeviceInfos.put(field.getName(), field.get("").toString());
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch(IllegalAccessException e) { e.printStackTrace(); }}}Copy the code
  • Obtain general system information (locate bugs more effectively for users with different Settings)
 /** * gets system general Settings */
    private void collectSystemInfos() {
        Field[] fields = Settings.System.class.getFields();
        for (Field field : fields) {
            if(! field.isAnnotationPresent(Deprecated.class) && field.getType() ==String.class) {
                try {
                    String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
                    if(value ! =null) { mSystemInfos.put(field.getName(), value); }}catch(IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code
  • Get security Settings
 /** * Obtain system security Settings */
    private void collectSecureInfos() {
        Field[] fields = Settings.Secure.class.getFields();
        for (Field field : fields) {
            if(! field.isAnnotationPresent(Deprecated.class) && field.getType() ==String.class
                    && field.getName().startsWith("WIFI_AP")) {
                try {
                    String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
                    if(value ! =null) { mSecureInfos.put(field.getName(), value); }}catch(IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code
  • Obtaining application memory information (permissions required)
/** * Get memory information */
    private String collectMemInfos() {
        BufferedReader br = null;
        StringBuffer sb = new StringBuffer();

        ArrayList<String> commandLine = new ArrayList<>();
        commandLine.add("dumpsys");
        commandLine.add("meminfo");
        commandLine.add(Integer.toString(Process.myPid()));
        try {
            java.lang.Process process = Runtime.getRuntime()
                    .exec(commandLine.toArray(new String[commandLine.size()]));
            br = new BufferedReader(new InputStreamReader(process.getInputStream()), 8192);

            while (true) {
                String line = br.readLine();
                if (line == null) {
                    break;
                }
                sb.append(line);
                sb.append("\n"); }}catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br ! =null) {
                try {
                    br.close();
                } catch(IOException e) { e.printStackTrace(); }}}return sb.toString();
    }Copy the code
  • Finally, store this information ininfosWith this data, we should be able to quickly locate the cause of the crash
 /** * get device parameters ** @param context */
    private void collectInfos(Context context, Throwable ex) {
        mExceptionInfos = collectExceptionInfos(ex);
        collectPackageInfos(context);
        collectBuildInfos();
        collectSystemInfos();
        collectSecureInfos();
        mMemInfos = collectMemInfos();

        // Stores the information in a total Map for the upload action callback
        infos.put(EXCEPETION_INFOS_STRING, mExceptionInfos);
        infos.put(PACKAGE_INFOS_MAP, mPackageInfos);
        infos.put(BUILD_INFOS_MAP, mDeviceInfos);
        infos.put(SYSTEM_INFOS_MAP, mSystemInfos);
        infos.put(SECURE_INFOS_MAP, mSecureInfos);
        infos.put(MEMORY_INFOS_STRING, mMemInfos);
    }Copy the code

Write Crash logs

  1. Write crash data to local file (I only collected exception information and application information here, you can splice other data according to your own needs)
 /** * Writes crash log information to a local file */
    private String saveCrashInfo2File() {
        StringBuffer mStringBuffer = getInfosStr(mPackageInfos);
        mStringBuffer.append(mExceptionInfos);
        // Save the file and set the file name
        String mTime = formatter.format(new Date());
        String mFileName = "CrashLog-" + mTime + ".log";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            try {
                File mDirectory = new File(mDirPath);
                Log.v(TAG, mDirectory.toString());
                if(! mDirectory.exists()) mDirectory.mkdirs(); FileOutputStream mFileOutputStream =new FileOutputStream(mDirectory + File.separator + mFileName);
                mFileOutputStream.write(mStringBuffer.toString().getBytes());
                mFileOutputStream.close();
                return mFileName;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch(IOException e) { e.printStackTrace(); }}return null;
    }

 /** * Converts HashMap traversal to StringBuffer */
    @NonNull
    public static StringBuffer getInfosStr(ConcurrentHashMap<String.String> infos) {
        StringBuffer mStringBuffer = new StringBuffer();
        for (Map.Entry<String.String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            mStringBuffer.append(key + "=" + value + "\r\n");
        }
        return mStringBuffer;
    }Copy the code
  1. Since each application uploads to a different server or data type, for better scalability, we use interface callbacks to dump all the data to the application for implementation (I used the Bmob backend cloud for demonstration purposes).




 /** * Upload crash information to the server */
    public void uploadCrashMessage(ConcurrentHashMap<String.Object> infos) {
        mCrashUploader.uploadCrashMessage(infos);
    }

 /** * Crash message upload interface callback */
    public interface CrashUploader {
        void uploadCrashMessage(ConcurrentHashMap<String.Object> infos);
    }Copy the code

use

 /** * Initializes the crash handler */
    private void initCrashHandler() {

        mCrashUploader = new RCrashHandler.CrashUploader() {
            @Override
            public void uploadCrashMessage(ConcurrentHashMap<String.Object> infos) {
                CrashMessage cm = new CrashMessage();
                ConcurrentHashMap<String.String> packageInfos = (ConcurrentHashMap<String.String>) infos.get(RCrashHandler.PACKAGE_INFOS_MAP);
                cm.setDate(DateTimeUitl.getCurrentWithFormate(DateTimeUitl.sysDateFormate));
                cm.setVersionName(packageInfos.get(RCrashHandler.VERSION_NAME));
                cm.setVersionCode(packageInfos.get(RCrashHandler.VERSION_CODE));
                cm.setExceptionInfos(((String) infos.get(RCrashHandler.EXCEPETION_INFOS_STRING)));
                cm.setMemoryInfos((String) infos.get(RCrashHandler.MEMORY_INFOS_STRING));
                cm.setDeviceInfos(RCrashHandler.getInfosStr((ConcurrentHashMap<String.String>) infos
                        .get(RCrashHandler.BUILD_INFOS_MAP)).toString());
                cm.setSystemInfoss(RCrashHandler.getInfosStr((ConcurrentHashMap<String.String>) infos
                        .get(RCrashHandler.SYSTEM_INFOS_MAP)).toString());
                cm.setSecureInfos(RCrashHandler.getInfosStr((ConcurrentHashMap<String.String>) infos
                        .get(RCrashHandler.SECURE_INFOS_MAP)).toString());
                cm.save(new SaveListener<String>() {
                    @Override
                    public void done(String s, BmobException e) {
                        if (e == null) {
                            RLog.e("Upload successful!");

                        } else {
                            RLog.e("Uploading Bmob failed error code:"+ e.getErrorCode()); }}}); }}; RCrashHandler.getInstance(FileUtils.getRootFilePath() +"EasySport/crashLog")
                .init(mAppContext, mCrashUploader);
    }Copy the code

Some attention

Process.killprocess (process.mypid ())); And the System. The exit (1); The application will automatically restart for three times, which affects user experience

So we used a crude method, which is to have it open a CrashActivity that we set ourselves to improve the user experience of our application

  ** @param ex * @return true: If the error message is processed, the error message will be sent. Otherwise return false. */
    private boolean catchCrashException(Throwable ex) {
        if (ex == null) {
            return false;
        }

// Launch our custom page
        new Thread() {
            public void run() {
// Looper.prepare();
// toast.maketext (mContext, "sorry, program exception, about to exit ", 0).show();
// Looper.loop();
                Intent intent = new Intent();
                intent.setClass(mContext, CrashActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                ActivityCollector.finishAll();
                mContext.startActivity(intent);
            }
        }.start();

        // Collect device parameter information
        collectInfos(mContext, ex);
        // Save the log file
        saveCrashInfo2File();
        // Upload crash information
        uploadCrashMessage(infos);

        return true;
    }Copy the code

CrashActivity depends on your needs. A Sorry message or some interactive feedback is ok.

The last

CrashHandler the whole writing down idea is three steps: 1, exception capture 2, information data collection 3, data write local and upload server project address: This is a random project of mine that CrashHandler is mainly used in Util of rbase and MyApplication of app