primers
In the complex project environment, due to historical code, huge business complex, containing a variety of third-party libraries, so in the caton, it is difficult to locate exactly where there is a problem, which is an Activity/fragments, even know jumped at thousands of lines of class and were transferred to, the result is go away with it.
In fact, in many cases, the lag is not necessary, they may be related to the model, environment, operation, etc., there is an accident, even if it happens, and then check the mountain logCAT, it is not necessarily able to find the cause of the lag.
BlockCanary is designed to solve this problem. Farewell to the point and debugging, where stuck, at a glance.
Introduce a,
BlockCanary is a performance of the Android platform a noninvasive monitoring component, application only needs to implement an abstract class, provide some context, the component needs can be applied in use at ordinary times when testing on the main thread of various card slow problem, and through the component provides all kinds of information analysis the reason and repair. Official address: Android Performance Monitor.
BlockCanary monitors the main thread operations transparently and outputs useful information to help developers analyze and locate problems and optimize applications quickly. Its characteristics are:
- Non-intrusive, simple two lines to turn on monitoring, no need to poke around and break code elegance.
- Precise. The output information helps locate the problem (down to the line), rather than taking your time like Logcat.
Currently includes the core monitoring output file, as well as the UI display card information function.
Current problem: Because of the need to fetch CPU information, after API 26 (Android O), the /proc/stat directory cannot be accessed by normal applications except system-level applications, so this plugin is almost dead, but it doesn’t stop us from learning.
Two, use method
2.1 introduced
dependencies {
compile 'com. Making. Markzhai: blockcanary - android: 1.5.0'
// This can be used only if BlockCanary is enabled in the debug package for lag monitoring and prompting
debugCompile 'com. Making. Markzhai: blockcanary - android: 1.5.0'
releaseCompile 'com. Making. Markzhai: blockcanary - no - op: 1.5.0'
}
Copy the code
2.2 the use of
In the Application:
public class DemoApplication extends Application {
@Override
public void onCreate(a) {
// Initializes the call in the main process
BlockCanary.install(this.newAppBlockCanaryContext()).start(); }}Copy the code
Inherit BlockCanaryContext and implement your own AppBlockCanaryContext:
public class AppBlockCanaryContext extends BlockCanaryContext {
// Implement various contexts, including application identifier, user UID, network type, slow judgment threshold, Log store location, etc
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier(a) {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid(a) {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType(a) {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration(a) {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold(a) {
return 1000;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @return dump interval (in millis)
*/
public int provideDumpInterval(a) {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath(a) {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification(a) {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List<String> concernPackages(a) {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack(a) {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List<String> provideWhiteList(a) {
LinkedList<String> whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList(a) {
return true;
}
/** * Block interceptor, developer may provide their own actions. */
public void onBlock(Context context, BlockInfo blockInfo) {}}Copy the code
Third, the principle of
Check out my previous article: The Message loop model in Android
Using Android’s message handling mechanism, Looper. Java:
private static Looper sMainLooper; // guarded by Looper.class./**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper(a) {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}/** Returns the application's main looper, which lives in the main thread of the application. */
public static Looper getMainLooper(a) {
synchronized (Looper.class) {
returnsMainLooper; }}Copy the code
The main thread of the entire application, this one looper, no matter how many handlers there are, it’s going to end up back here.
Looper’s loop method has this section:
public static void loop(a) {...for(;;) {...// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if(logging ! =null) {
logging.println(">>>>> Dispatching to " + msg.target + "" +
msg.callback + ":" + msg.what);
}
msg.target.dispatchMessage(msg);
if(logging ! =null) {
logging.println("<<<<< Finished to " + msg.target + ""+ msg.callback); }... }}Copy the code
MLogging is called before and after each message processing and if the main thread is stuck it is stuck within dispatchMessage.
Core flow chart (source author’s blog) :
BlockCanary starts a thread that stores the current UI thread stack information in mThreadStackEntries and CPU information in mCpuInfoEntries, each with a time pinch key.
BlockCanary registers logging to get the event start and end time. If the event processing time exceeds the threshold (1s by default), search mThreadStackEntries for stack information between T1 and T2, and search mCpuInfoEntries for CPU and memory information between T1 and T2. After formatting the information, save it to a local file and notify the user. This component utilizes the message queue processing mechanism of the main thread through
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
Copy the code
And judge start and end in mainLooperPrinter to obtain the start and end time of the message of the main thread dispatch, and determine that the time exceeds the threshold (such as 2000 ms), and dump various information to provide developers to analyze performance bottlenecks.
.@Override
public void println(String x) {
if(! mStartedPrinting) { mStartTimeMillis = System.currentTimeMillis(); mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis(); mStartedPrinting =true;
} else {
final long endTime = System.currentTimeMillis();
mStartedPrinting = false;
if(isBlock(endTime)) { notifyBlockEvent(endTime); }}}private boolean isBlock(long endTime) {
returnendTime - mStartTimeMillis > mBlockThresholdMillis; }...Copy the code
Four, source code interpretation
BlockCanary.install(this.new AppBlockContext()).start();
Copy the code
First let’s look at its entry, the install method:
/**
* Install {@link BlockCanary}
*
* @param context Application context
* @param blockCanaryContext BlockCanary context
* @return {@link BlockCanary}
*/
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
BlockCanaryContext.init(context, blockCanaryContext);
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
Copy the code
Three lines of code are called here:
- Call the init() method and record
Application
andBlockCanaryContext
To provide Context and configuration parameters for subsequent processing (e.g., lag threshold, whether to display notifications, etc.) - Call the setEnabled() method to check whether the desktop displays a yellow logo icon
- Call the get() method to create an instance of BlockCanary, and create a BlockCanaryInternals instance, which is assigned to the mBlockCanaryCore attribute, to handle the subsequent process
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
sInstance = blockCanaryContext;
}
Copy the code
So this init method is just doing an assignment, assigning the context that we passed in.
Let’s move on to what BlockCanary.start() does:
public void start(a) {
if(! mMonitorStarted) { mMonitorStarted =true; Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor); }}Copy the code
The start() method does only one thing: set a Printer for Looper
The println() method of mblockCanaryCore.monitor is called before and after Looper processes the message.
Mblockcanarycore. monitor is the LooperMonitor member property of BlockCanaryInternals
class LooperMonitor implements Printer {...@Override
public void println(String x) {
// If StopWhenDebugging, do not detect
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if(! mPrintingStarted) { mStartTimestamp = System.currentTimeMillis(); mStartThreadTimestamp = SystemClock.currentThreadTimeMillis(); mPrintingStarted =true;
startDump(); // Get the call stack and CPU information in the child thread
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) { // Check whether the threshold is exceeded
notifyBlockEvent(endTime);
}
stopDump(); // Stop getting call stack and CPU information}}// Check whether the threshold is exceeded
private boolean isBlock(long endTime) {
returnendTime - mStartTimestamp > mBlockThresholdMillis; }... }Copy the code
LooperMonitor’s println() is the core, and the code is very simple:
- Before Looper processes the message, get the current time and save it, call startDump() to start a task that periodically collects call stack /CPU information, etc
- Looper processes the message, gets the current time, and determines whether our custom threshold isBlock(endTime) has been exceeded. If so, notifyBlockEvent(endTime) is called to notify the subsequent process
- Call stopDump() to stop the task of getting the call stack and CPU
StartDump collects the following information:
- Basic information: model, number of CPU cores, process name, memory, version number, etc
- Time information: actual time, main thread clock time, start time and end time
- CPU information: whether the CPU is busy within a time range, system CPU/ application CPU ratio, I/O ratio within a time range – – CPU usage
- Stack information: The most recent stack before a crash occurred
Five, the summary
Blockcanary makes perfect use of the message mechanism on Android. Set a Printer for Looper. By recording stack and CPU information, calculate the message processing time of the main thread and retrieve the stack and CPU information at this time to help analyze the cause of the lag.
BlockCanary — Easy to find the culprit behind Android App interface delays
Making: BlockCanary