首页 Android三方开源库之BlockCanary源码分析
文章
取消

Android三方开源库之BlockCanary源码分析

我们手机屏幕帧率通常是60,也就意味着每秒钟有60个画面出现,即16.6ms就要有一个画面渲染出来,Android系统每隔16.6ms发出一个Vsync信号,触发对View进行渲染,如果在这个时间内渲染成功,那么画面正常显示,否则就会出现丢帧的情况,如果掉帧频率很高,也就导致了卡顿。

我们回顾一下View刷新机制,App启动时,会通过ActivityThread类的main方法,创建一个主线程Looper,并通过Looper.loop方法不断轮询,从MessageQueue队列中取出Message来更新UI,而UI更新往往会通过ViewRootImpl类的scheduleTraversals方法来进行一次View树的遍历绘制,最终通过Choreographer的postCallback将该绘制任务添加到待执行队列里面,由主线程looper的loop方法不断取出消息执行任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Looper.java

public static void loop() {
    final Looper me = myLooper();
    ...
    // 获取当前Looper的消息队列
    final MessageQueue queue = me.mQueue;
    ...
    for (; ; ) {
        // 取出一个消息
        Message msg = queue.next(); // might block
        ...
        // "此mLogging可通过Looper.getMainLooper().setMessageLogging方法设置自定义"
        final Printer logging = me.mLogging;
        if (logging != null) {// 消息处理前
            // "若mLogging不为null,则此处可回调到该类的println方法"
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                                msg.callback + ": " + msg.what);
        }

        ...
        try {
           // 消息处理
           msg.target.dispatchMessage(msg);
           dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
           if (traceTag != 0) {
               Trace.traceEnd(traceTag);
           }
        }
        ...

        if (logging != null) {// 消息处理后
            // "消息处理后,也可调用logging的println方法"
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}

通过上面方法可以看出,最终的消息处理发生在dispatchMessage方法中,所以对于设置一个Printer可以记录该方法的耗时。那么其实BlockCanary原理其实也是这样,通过自定义Printer来实现println方法,然后在println方法中监控是否有卡顿发生,从上面也可以发现,logging.println成对出现在消息处理方法的前后,那么就可以通过在自定义的pringln方法中定义标识来分辨消息前后,并计算时间差与我们自己设置的阈值进行对比,从而判断卡顿是否发生。

源码分析

BlockCanary通过install方法完成初始化,并执行start方法:

1
2
3
4
5
6
7
8
9
// BlockCanary.java

public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
    // 根据用户配置参数进行初始化
    BlockCanaryContext.init(context, blockCanaryContext);
    // 开启配置用户消息栏
    setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
    return get();
}

上面方法中先根据用户继承的BlockCanaryContext,配置参数初始化,然后开启消息栏,最后通过get方法返回一个BlockCanary单例对象。

在BlockCanary构造方法中:

1
2
3
4
5
6
7
8
9
10
// BlockCanary.java
private BlockCanary() {
    BlockCanaryInternals.setContext(BlockCanaryContext.get());
    mBlockCanaryCore = BlockCanaryInternals.getInstance();
    mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
    if (!BlockCanaryContext.get().displayNotification()) {
        return;
    }
    mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}

上面代码先是初始化blockCanaryInternals调度类,然后为该类添加拦截器责任链,当用户开启消息栏通知,就在卡顿发生时通过DisplayService发送通知栏消息。

紧接着我们看下这个调度类的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// BlockCanaryInternals.java
public BlockCanaryInternals() {
    // 初始化栈采集器
    stackSampler = new StackSampler(
        Looper.getMainLooper().getThread(),
        sContext.provideDumpInterval());
	// 实例化CPU采集器
    cpuSampler = new CpuSampler(sContext.provideDumpInterval());

    // 设置LooperMonitor,并实现onBlockEvent回调,在触发阈值时执行
    setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {

        @Override
        public void onBlockEvent(long realTimeStart, long realTimeEnd,
                                 long threadTimeStart, long threadTimeEnd) {
            // Get recent thread-stack entries and cpu usage
            ArrayList<String> threadStackEntries = stackSampler
                .getThreadStackEntries(realTimeStart, realTimeEnd);
            if (!threadStackEntries.isEmpty()) {
                BlockInfo blockInfo = BlockInfo.newInstance()
                    .setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
                    .setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
                    .setRecentCpuRate(cpuSampler.getCpuRateInfo())
                    .setThreadStackEntries(threadStackEntries)
                    .flushString();
                LogWriter.save(blockInfo.toString());

                if (mInterceptorChain.size() != 0) {
                    for (BlockInterceptor interceptor : mInterceptorChain) {
                        interceptor.onBlock(getContext().provideContext(), blockInfo);
                    }
                }
            }
        }
    }, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));

    LogWriter.cleanObsolete();
}

在该构造函数中进行一系列的初始化操作,初始化栈采集器,初始化CPU采集器,初始化LooperMonitor,当准备工作完成后,就执行BlockCanary的start方法:

1
2
3
4
5
6
7
// BlockCanary.java
public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}

在这里设置了主线程Looper的setMessageLogging方法,参数monitor就是上面创建的LooperMonitor实例。

也就是我们前面介绍的自定义Printer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class LooperMonitor implements Printer {
    ...
    @Override
    public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if (!mPrintingStarted) {
            // 记录开始时间
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            // 开始采集栈和CPU信息
            startDump();
        } else {
            // 记录结束时间
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            // 判断耗时是否超过阈值
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            stopDump();
        }
    }
    private boolean isBlock(long endTime) {
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }
    private void notifyBlockEvent(final long endTime) {
        final long startTime = mStartTimestamp;
        final long startThreadTime = mStartThreadTimestamp;
        final long endThreadTime = SystemClock.currentThreadTimeMillis();
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
            }
        });
    }
    
    private void startDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.start();
        }

        if (null != BlockCanaryInternals.getInstance().cpuSampler) {
            BlockCanaryInternals.getInstance().cpuSampler.start();
        }
    }

    private void stopDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.stop();
        }

        if (null != BlockCanaryInternals.getInstance().cpuSampler) {
            BlockCanaryInternals.getInstance().cpuSampler.stop();
        }
    }
        
}

上面代码通过成员变量mPrintingStarted来判断方法处理前和后,根据我们设置的阈值mBlockThresholdMillis来判断是否产生卡顿,该值默认是3s,如果发生卡顿,则进入notifyBlockEvent方法,通过调用HandlerThread内部创建的Handler的post方法,将任务交给子线程来处理。该任务执行的逻辑即是mBlockListener的onBlockEvent方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// HandlerThreadFactory.java
final class HandlerThreadFactory {

    private static HandlerThreadWrapper sLoopThread = new HandlerThreadWrapper("loop");
    private static HandlerThreadWrapper sWriteLogThread = new HandlerThreadWrapper("writer");

    private HandlerThreadFactory() {
        throw new InstantiationError("Must not instantiate this class");
    }

    public static Handler getTimerThreadHandler() {
        return sLoopThread.getHandler();
    }

    public static Handler getWriteLogThreadHandler() {
        return sWriteLogThread.getHandler();
    }

    private static class HandlerThreadWrapper {
        private Handler handler = null;

        public HandlerThreadWrapper(String threadName) {
            HandlerThread handlerThread = new HandlerThread("BlockCanary-" + threadName);
            handlerThread.start();
            handler = new Handler(handlerThread.getLooper());
        }

        public Handler getHandler() {
            return handler;
        }
    }
}

紧接着在该方法里面,从栈采集器里面获取记录信息,然后构建BlockInfo对象,并将该对象信息写入日志文件。最后遍历拦截器进行通知,如果还有DisplayService,就会发送前台通知。

另外上面开始后会调用startDump方法,该方法会开启采集栈和CPU信息,处理工作分别是由StackSampler和CpuSampler类负责,通过调用start方法开始处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// AbstractSampler.java
abstract class AbstractSampler {

    private static final int DEFAULT_SAMPLE_INTERVAL = 300;

    protected AtomicBoolean mShouldSample = new AtomicBoolean(false);
    protected long mSampleInterval;

    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            doSample();
			// 延迟卡顿阈值执行任务
            if (mShouldSample.get()) {
                HandlerThreadFactory.getTimerThreadHandler()
                        .postDelayed(mRunnable, mSampleInterval);
            }
        }
    };

    public AbstractSampler(long sampleInterval) {
        if (0 == sampleInterval) {
            sampleInterval = DEFAULT_SAMPLE_INTERVAL;
        }
        mSampleInterval = sampleInterval;
    }

    public void start() {
        if (mShouldSample.get()) {
            return;
        }
        mShouldSample.set(true);
        // 移除上一次任务
        HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
        // 延迟卡顿阀值*0.8 的时间执行Runnable
        HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
                BlockCanaryInternals.getInstance().getSampleDelay());
    }

    public void stop() {
        if (!mShouldSample.get()) {
            return;
        }
        mShouldSample.set(false);
        HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
    }

    abstract void doSample();
}

在start方法中,会延时执行doSample方法,该方法是抽象方法,由StackSampler和CpuSampluer覆写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// StackSampler.java
protected void doSample() {
    StringBuilder stringBuilder = new StringBuilder();

    for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
        stringBuilder
            .append(stackTraceElement.toString())
            .append(BlockInfo.SEPARATOR);
    }
	
    // 根据LRU算法来移除最早添加进来的数据
    synchronized (sStackMap) {
        if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
            sStackMap.remove(sStackMap.keySet().iterator().next());
        }
        // 以当前时间为key,存储堆栈信息
        sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
    }
}

// CpuSampler.java
@Override
protected void doSample() {
    BufferedReader cpuReader = null;
    BufferedReader pidReader = null;

    try {
        cpuReader = new BufferedReader(new InputStreamReader(
            new FileInputStream("/proc/stat")), BUFFER_SIZE);
        String cpuRate = cpuReader.readLine();
        if (cpuRate == null) {
            cpuRate = "";
        }

        if (mPid == 0) {
            mPid = android.os.Process.myPid();
        }
        pidReader = new BufferedReader(new InputStreamReader(
            new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
        String pidCpuRate = pidReader.readLine();
        if (pidCpuRate == null) {
            pidCpuRate = "";
        }
		// 解析CPU信息
        parse(cpuRate, pidCpuRate);
    } catch (Throwable throwable) {
        Log.e(TAG, "doSample: ", throwable);
    } finally {
        try {
            if (cpuReader != null) {
                cpuReader.close();
            }
            if (pidReader != null) {
                pidReader.close();
            }
        } catch (IOException exception) {
            Log.e(TAG, "doSample: ", exception);
        }
    }
}

// CpuSampler.java
private void parse(String cpuRate, String pidCpuRate) {
    String[] cpuInfoArray = cpuRate.split(" ");
    if (cpuInfoArray.length < 9) {
        return;
    }

    long user = Long.parseLong(cpuInfoArray[2]);
    long nice = Long.parseLong(cpuInfoArray[3]);
    long system = Long.parseLong(cpuInfoArray[4]);
    long idle = Long.parseLong(cpuInfoArray[5]);
    long ioWait = Long.parseLong(cpuInfoArray[6]);
    long total = user + nice + system + idle + ioWait
        + Long.parseLong(cpuInfoArray[7])
        + Long.parseLong(cpuInfoArray[8]);

    String[] pidCpuInfoList = pidCpuRate.split(" ");
    if (pidCpuInfoList.length < 17) {
        return;
    }

    long appCpuTime = Long.parseLong(pidCpuInfoList[13])
        + Long.parseLong(pidCpuInfoList[14])
        + Long.parseLong(pidCpuInfoList[15])
        + Long.parseLong(pidCpuInfoList[16]);

    if (mTotalLast != 0) {
        StringBuilder stringBuilder = new StringBuilder();
        long idleTime = idle - mIdleLast;
        long totalTime = total - mTotalLast;

        stringBuilder
            .append("cpu:")
            .append((totalTime - idleTime) * 100L / totalTime)
            .append("% ")
            .append("app:")
            .append((appCpuTime - mAppCpuTimeLast) * 100L / totalTime)
            .append("% ")
            .append("[")
            .append("user:").append((user - mUserLast) * 100L / totalTime)
            .append("% ")
            .append("system:").append((system - mSystemLast) * 100L / totalTime)
            .append("% ")
            .append("ioWait:").append((ioWait - mIoWaitLast) * 100L / totalTime)
            .append("% ]");

        synchronized (mCpuInfoEntries) {
            mCpuInfoEntries.put(System.currentTimeMillis(), stringBuilder.toString());
            if (mCpuInfoEntries.size() > MAX_ENTRY_COUNT) {
                for (Map.Entry<Long, String> entry : mCpuInfoEntries.entrySet()) {
                    Long key = entry.getKey();
                    mCpuInfoEntries.remove(key);
                    break;
                }
            }
        }
    }
    mUserLast = user;
    mSystemLast = system;
    mIdleLast = idle;
    mIoWaitLast = ioWait;
    mTotalLast = total;

    mAppCpuTimeLast = appCpuTime;
}

综上,可以看出按照固定时间间隔循环执行采集任务,在结束时调用stop方法移除该任务。

总结

最后用一张图总结其工作流程图:

image-20220902162859753

本文由作者按照 CC BY 4.0 进行授权

Android Framework之Binder原理分析

Android三方开源库之LeakCanary2.4源码分析