# Analysis of blockcanary source code

Time：2022-5-6

### Analysis of blockcanary source code

Before explaining the source code of blockcanal, we still need to add some pre knowledge points. This article doesn’t talk about the principle of handler. Students who don’t understand it go to Baidu to have a look.

#### What is carton

Before we talk about the Caton problem, we need to talk about itFrame rateThis concept. The frame rate is the frequency at which bitmap images in frames appear continuously on the display. I will give an example, movie play. A movie is actually a collection of many photos (frames), so why does it look like a continuous process? Because there is more than one picture in the film every second. In fact, the number of pictures in a movie is usually 20-30 per second. If 24 pictures appear in a movie every second, the frame rate of the movie is 24. The frame rate is how many frames appear in a second.

I know what frame rate is, so the problem is, why is there a jam? Caton’s performance in our vision is that the original animation picture is smooth, but now it is not smooth. As we mentioned above, animation is actually composed of many pictures. If in a 24 frame movie, there is a second suddenly, and the frame is dropped in this second. That is, the original 0 The picture of 23 becomes 0 10… 12… 23. If a frame in the middle is not rendered, it will not be smooth visually. That’s the Caton phenomenon. The above is the phenomenon of Caton in the film. What about our Android system?

#### Android rendering mechanism

Before the advent of high brush mobile phones, the frame rate of our mobile phone screen was 60. It means that 60 images will appear in one second. Then there must be a picture rendering in 16ms** The Android system sends a Vsync signal every 16ms to trigger the rendering of the UI. If each rendering is successful, 60 frames required for a smooth picture can be achieved. In order to achieve 60fps, this means that most operations of the program must be completed within 16ms. If it exceeds 16ms, the frame may be lost** If the frequency of frame dropping is very high, that is, it leads to jamming.

#### Analysis of blockcanary source code

So in Android,`BlockCanary`How to help us do the carton test. Today we’ll talk about it`BlockCanary`Check the principle of Caton.

Generally, we start our Caton detection through the following code methods.

``````public class DemoApplication extends Application {
@Override
public void onCreate() {
// ...
// Do it on main process
BlockCanary.install(this, new AppBlockCanaryContext()).start();
}
}
``````

This code mainly has two parts: one is install and the other is start. Let’s look at the install section first

##### Install phase
###### BlockCanary#install()
``````public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
//BlockCanaryContext. Init will save the ApplicationContext of the application and the configuration parameters set by the user
BlockCanaryContext.init(context, blockCanaryContext);
//Etenabled will be enabled according to the user's notification bar message configuration
return get();
}``````
###### BlockCanary#get()
``````//A blockcanary object is created using a singleton
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}``````
###### BlockCanary()
``````private BlockCanary() {
//Initialize the blockcanaryinternal scheduling class
BlockCanaryInternals.setContext(BlockCanaryContext.get());
mBlockCanaryCore = BlockCanaryInternals.getInstance();
//Add an interceptor (chain of responsibility) for blockcanaryinternals. Blockcanarycontext is an empty implementation of blockinterceptor
return;
}
//Displayservice is only added when the notification bar message is opened. When a jam occurs, the notification bar message will be initiated through displayservice

}``````
###### BlockCanaryInternals.getInstance()
``````static BlockCanaryInternals getInstance() {
if (sInstance == null) {
synchronized (BlockCanaryInternals.class) {
if (sInstance == null) {
sInstance = new BlockCanaryInternals();
}
}
}
return sInstance;
}
``````
###### BlockCanaryInternals
``````public BlockCanaryInternals() {
//Initialize stack collector
stackSampler = new StackSampler(
sContext.provideDumpInterval());
//Initialize CPU collector
cpuSampler = new CpuSampler(sContext.provideDumpInterval());

//Initialize loopermonitor and implement the callback of onblockevent, which will be called after triggering the threshold, which is more important
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {

@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
BlockInfo blockInfo = BlockInfo.newInstance()
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.flushString();
LogWriter.save(blockInfo.toString());

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

LogWriter.cleanObsolete();
}``````

After the initialization of install is completed, the start () method will be called. The implementation is as follows:

##### Start phase
###### BlockCanary#start()
``````//BlockCanary#start()
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
//Set the monitor in the mainloopmonitor
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}``````

The implementation is also relatively simple, that is, get the looper of the main thread, and then set the loopermonitor created in the previous step to messagelogging in the looper of the main thread.

Here, and then? Lying trough, no, I was also very confused when I first looked at the source code here. Then I went to GitHub to see it. Then, I saw such a picture.

From this figure, I can see that the real start of detection is not start (), but the loop () function in looper

###### Looper#loop
``````public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}

me.mInLoop = true;
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ ".slow", 0);

boolean slowDeliveryDetected = false;

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}

``````

The code in loop () is very long. We don’t need to pay too much attention to other parts when explaining blockcanary. Remember what we did in start? We set it`setMessageLogging`。 So take a look first`setMessageLogging`method

###### Looper#setMessageLogging
``````public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
``````

In fact, it will be created`LooperMonitor`Assigned to mlogging, then we only need to focus on the code of mlogging in loop(). We found that println was called twice. One is in`msg.target.dispatchMessage(msg)`Before, one was in`msg.target.dispatchMessage(msg)`After that. That is to say, these two calls, one is before processing the signal and the other is after processing the signal. Then, by implementing the println method in loopermonitor, we can get some time difference. So, next we’ll look at the println method in loopermonitor

###### MainLooper#println()
``````//MainLooper#println()
@Override
public void println(String x) {
//If the debug mode is used again, monitoring will not be performed
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
If (! Mprintingstarted) {// println executed before dispatchmesage
//Record start time
mStartTimestamp = System.currentTimeMillis();
mPrintingStarted = true;
//Start collecting stack and CPU Information
startDump();
}Else {// println executed after dispatchmesage
//Get end time
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//Determine whether the time consumption exceeds the threshold
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}

//Determine whether the threshold is exceeded
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;// This threshold is set by ourselves
}

//If the threshold value is exceeded, call back the monitoring of Caton, indicating that Caton is blocked
private void notifyBlockEvent(final long endTime) {
final long startTime = mStartTimestamp;
@Override
public void run() {