Template method pattern and practice of Java design pattern: application crash handling

Posted by invarbrass on Tue, 07 Jan 2020 17:22:12 +0100

Template method mode and practice: application crash handling

This paper mainly introduces the template method pattern, and uses the template method pattern to develop an extensible Android application crash handling tool class.

Principle and Application

  • AsyncTask
  • Drawing process of View
    Wait..

Template method mode focuses on
-Encapsulation of immutable part
-Extract common part code for easy maintenance

For example, the drawing process of View:

  • public final void measure(..) –> prptected void onMeasure(..)
  • public final void layout(..) –> prptected void onLayout(..)
  • public final void draw(..) –> prptected void onDraw(..)

xxx method is defined as final, which means that the order of core logic cannot be modified; onMeasure can be overridden, which means that this part can be customized (Extended).

Determine the process and abstract the specific implementation details.

Actual combat: application crash handling

Use the template method pattern to implement an application crash handling tool class.

Two processes of crash handling:
-Collect crash data
-Save crash data

We abstract these two specific details and let the subclass implement them:

public abstract class CrashHandler<T> {

    abstract T collectData(Context context, Thread t, Throwable e);

    abstract void saveData(Context context, T data);
}

Then write the logic of crash handling:

public void install(Context context) {
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(final Thread t, final Throwable e) {
                new Thread() {
                    @Override
                    public void run() {
                        //Concrete logic of implementing subclass
                        saveData(mContext, collectData(mContext, t, e));
                        //Exit procedure
                        android.os.Process.killProcess(android.os.Process.myPid());
                        System.exit(1);
                    }
                }.start();
            }
        });
    }

The whole tool code is as follows:

Abstract class CrashHandler:

package com.newsapp.tool;

import android.content.Context;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

/**
 * @author mingC
 * @date 2018/8/5
 */
public abstract class CrashHandler<T> {

    private Context mContext;

    /**
     * Crash data collection
     * @param t Crash thread
     * @param e Cause of collapse
     * @return Crash data
     */
    abstract T collectData(Context context, Thread t, Throwable e);

    /**
     * To save the crash data, you can save it to a file or upload it to the server here
     * @param data
     */
    abstract void saveData(Context context, T data);

    private void showTipToast() {
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "I'm sorry,Program exception is about to exit.", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
    }

    public void install(Context context) {
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(final Thread t, final Throwable e) {
                e.printStackTrace();
                showTipToast();
                Log.e("CrashHandler", "thread" + t.getName() + "An uncaught exception occurred, crash processing in progress...");
                final long beginTime = System.currentTimeMillis();
                new Thread() {
                    @Override
                    public void run() {
                        long transcatTime = System.currentTimeMillis() - beginTime;
                        //Concrete logic of implementing subclass
                        saveData(mContext, collectData(mContext, t, e));
                        Log.e("CrashHandler", "Crash processing completed at(ms): " + transcatTime);
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e1) {
                            e1.printStackTrace();
                        }
                        // Exit program, some models will fail to exit until ANR
                        android.os.Process.killProcess(android.os.Process.myPid());
                        System.exit(1);
                    }
                }.start();
            }
        });
    }
}

Subclass RealCrashHandler:

package com.newsapp.tool;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @author mingC
 * @date 2018/8/5
 */
public class RealCrashHandler extends CrashHandler<RealCrashHandler.CrashData>{

    @Override
    CrashData collectData(Context context, Thread t, Throwable e) {
        CrashData data = new CrashData();
        //Save exception information
        int s = 0;
        for (StackTraceElement stackTraceElement : e.getStackTrace()) {
            data.map.put("err-stack" + (s++), stackTraceElement.toString());
        }
        //Save application information
        PackageManager pm = context.getPackageManager();
        PackageInfo pi = null;
        try {
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e1) {
            e1.printStackTrace();
        }
        if (pi != null) {
            String versionName = pi.versionName == null ? "null" : pi.versionName;
            String versionCode = pi.versionCode + "";
            data.map.put("versionName", versionName);
            data.map.put("versionCode", versionCode);
        }
        //Save device information
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                data.map.put(field.getName(), field.get(null).toString());
                Log.d("CrashHandler", field.getName() + " : " + field.get(null));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return data;
    }


    public void readCrashInfo(Context context) {
        Log.d("RealCrashHandler", "Reading crash data");
        try {
            File file = new File(context.getFilesDir(), "crash-info");
            if (! file.exists()) {
                Log.d("RealCrashHandler", "No crash data");
                return;
            }
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line;
            while ((line = reader.readLine()) != null) {
                Log.d("RealCrashHandler", line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    void saveData(Context context, RealCrashHandler.CrashData data) {
        try {
            FileOutputStream out = new FileOutputStream(new File(context.getFilesDir(), "crash-info"));
            for (Map.Entry<String, String> entry : data.map.entrySet()) {
                String row = entry.getKey() + "-" + entry.getValue() + "\n";
                out.write(row.getBytes());
            }
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class CrashData {
        Map<String, String> map = new HashMap<>();
    }
}

Use to register in Application:

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        RealCrashHandler crashHandler = new RealCrashHandler();
        crashHandler.install(this);
        //To read the last crash data, you can upload the data at the time of crash. If the upload fails, you can also upload the data at the time of opening the next application
        crashHandler.readCrashInfo(this);
    }
}

Topics: Android Java