A simple Android JankDetecter design and implementation

3/10/2018 posted in  Android comments

0x00 Background

When we developing and releasing an Android Application, we might encounter issues like Frozen Frames(FF) or Janks(according to Android Developer), to detect Janks during developing or logging, we can add a simple detector to check whether an application is going through a Jank.

0x01 Detect strategy

To detect whether an application is on Jank state, we can post a Handler when MainThread looper starts a new message logging printer, we could override the default Printer, when Printer is start printing a new Main looper message, at the start of the Logging, main looper will dispatch a START_TAG(">>>>> Dispatching"), we can start a new LogMonitor instance, at the end of Logging, a END_TAG("<<<<< Finished") will be dispatched, if all method run in the time block end during the main thread dispatch END_TAG, we can remove the LogMonitor handler.

Code implementation

Start point

    // in MyApplication.java
    if (BuildConfig.DEBUG) {
        JankDetecter.start();
    }

Detecter class

public class JankDetecter {
    public static void start() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            private static final String START_TAG = ">>>>> Dispatching";
            private static final String END_TAG = "<<<<< Finished";
            @Override
            public void println(String x) {
                if (x.startsWith(START_TAG)) {
                    LogMonitor.getInstance().startMonitor();
                }
                if (x.startsWith(END_TAG)) {
                    LogMonitor.getInstance().removeMonitor();
                }
            }
        });
    }
}

LogMonitor class

public class LogMonitor {
    private static LogMonitor sInstance = new LogMonitor();
    private HandlerThread mLogThread = new HandlerThread("log");
    private Handler mIoHander;
    private static final long TIME_BLOCK = 500L;

    private LogMonitor() {
        mLogThread.start();
        mIoHander = new Handler(mLogThread.getLooper());
    }
    public static LogMonitor getInstance() {
        return sInstance;
    }
    public void startMonitor() {
        mIoHander.postDelayed(mLogRunnable, TIME_BLOCK);
    }
    public void removeMonitor() {
        mIoHander.removeCallbacks(mLogRunnable);
    }
    private static Runnable mLogRunnable = new Runnable() {
        @Override
        public void run() {
            StringBuilder stringBuilder = new StringBuilder();
            StackTraceElement[] stackTraces = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement s : stackTraces) {
                stringBuilder.append(s.toString() + "\n");
            }
        }
    };
}