原理
即整个应用的主线程,只有这一个looper,不管有多少handler,最后都会回到这里。
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() {
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() {
synchronized (Looper.class) {
return sMainLooper;
}
}
看下Looper的loop方法。
public static void loop() {
...
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);
}
...
}
}
在消息执行前后打印时间。根据时间判断是否卡顿。
核心流程图
源码分析
BlockCanary.install(this, new AppBlockCanaryContext()).start();
看下BlockCanary.install()
public final class BlockCanary {
private static final String TAG = "BlockCanary";
private static BlockCanary sInstance;
private BlockCanaryInternals mBlockCanaryCore;
private boolean mMonitorStarted = false;
private BlockCanary() {
(1) 赋值一个Context。
BlockCanaryInternals.setContext(BlockCanaryContext.get());
(2)获取一个单例看下构造方法
mBlockCanaryCore = BlockCanaryInternals.getInstance();
(3)添加两个回调
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
/**
* Install {@link BlockCanary}
*
* @param context Application context
* @param blockCanaryContext BlockCanary context
* @return {@link BlockCanary}
*/
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
(1) 里面就是一些赋值操作。
BlockCanaryContext.init(context, blockCanaryContext);
(2)是否通过DisplayActivity展示卡顿,默认展示
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
(3)返回单例看下构造方法
return get();
}
/**
* Get {@link BlockCanary} singleton.
*
* @return {@link BlockCanary} instance
*/
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
}
public class BlockCanaryContext implements BlockInterceptor {
private static Context sApplicationContext;
private static BlockCanaryContext sInstance = null;
public BlockCanaryContext() {
}
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
sInstance = blockCanaryContext;
}
public static BlockCanaryContext get() {
if (sInstance == null) {
throw new RuntimeException("BlockCanaryContext null");
} else {
return sInstance;
}
}
}
BlockCanary只是一个门面类,真正处理逻辑的是 BlockCanaryInternals
看下BlockCanaryInternals
public final class BlockCanaryInternals {
LooperMonitor monitor;
StackSampler stackSampler;
CpuSampler cpuSampler;
private static BlockCanaryInternals sInstance;
private static BlockCanaryContext sContext;
private List<BlockInterceptor> mInterceptorChain = new LinkedList<>();
public BlockCanaryInternals() {
(1)设置了主线程的Looper,所以采集的是主线程的堆栈信息。
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
(2)采集CPU的使用信息。
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
(3)创建一个Printer对象。
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
(4)如果主线程出现了卡顿,就会触发 onBlockEvent 回调方法,然后进行线程堆栈信息和cpu信息的采集。
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();
}
/**
* Get BlockCanaryInternals singleton
*
* @return BlockCanaryInternals instance
*/
static BlockCanaryInternals getInstance() {
if (sInstance == null) {
synchronized (BlockCanaryInternals.class) {
if (sInstance == null) {
sInstance = new BlockCanaryInternals();
}
}
}
return sInstance;
}
/**
* set {@link BlockCanaryContext} implementation
*
* @param context context
*/
public static void setContext(BlockCanaryContext context) {
sContext = context;
}
public static BlockCanaryContext getContext() {
return sContext;
}
void addBlockInterceptor(BlockInterceptor blockInterceptor) {
mInterceptorChain.add(blockInterceptor);
}
private void setMonitor(LooperMonitor looperPrinter) {
monitor = looperPrinter;
}
}
上面主要构造了一些卡顿监测和采集对象。
看下BlockCanary.start()
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
(1)将自定义的 Printer 赋值给主线程的 Looper。
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
看下LooperMonitor
class LooperMonitor implements Printer {
private static final int DEFAULT_BLOCK_THRESHOLD_MILLIS = 3000;
private long mBlockThresholdMillis = DEFAULT_BLOCK_THRESHOLD_MILLIS;
private long mStartTimestamp = 0;
private long mStartThreadTimestamp = 0;
private BlockListener mBlockListener = null;
private boolean mPrintingStarted = false;
private final boolean mStopWhenDebugging;
public interface BlockListener {
void onBlockEvent(long realStartTime,
long realTimeEnd,
long threadTimeStart,
long threadTimeEnd);
}
public LooperMonitor(BlockListener blockListener, long blockThresholdMillis, boolean stopWhenDebugging) {
if (blockListener == null) {
throw new IllegalArgumentException("blockListener should not be null.");
}
mBlockListener = blockListener;
mBlockThresholdMillis = blockThresholdMillis;
mStopWhenDebugging = stopWhenDebugging;
}
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
(1)是否卡顿
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();
(2) 卡顿回调
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
private void startDump() {
(3)线程堆栈信息
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
(4)CPU使用信息
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();
}
}
}
代码还是比较简单,
- 消息触发前获取堆栈和CPU的信息,
- 根据消息执行前后时间间隔,判断是否卡顿,卡顿就回调卡顿事件。
- 消息执行后停止获取堆栈和CPU的信息。
看下StackSampler
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() {
(1)采集信息
doSample();
(2)间隔一段时间再采集
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);
(1)看下mRunnable
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
abstract void doSample();
}
class StackSampler extends AbstractSampler {
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
(1)时间为key,线程堆栈信息为value放入LinkedHashMap
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
}
很简单就是
每隔一定时间做一次线程堆栈信息的采集,放入到LinkedHashMap。
看下CpuSampler
class CpuSampler extends AbstractSampler {
private static final String TAG = "CpuSampler";
private static final int BUFFER_SIZE = 1000;
/**
* TODO: Explain how we define cpu busy in README
*/
private final int BUSY_TIME;
private static final int MAX_ENTRY_COUNT = 10;
private final LinkedHashMap<Long, String> mCpuInfoEntries = new LinkedHashMap<>();
private int mPid = 0;
private long mUserLast = 0;
private long mSystemLast = 0;
private long mIdleLast = 0;
private long mIoWaitLast = 0;
private long mTotalLast = 0;
private long mAppCpuTimeLast = 0;
public CpuSampler(long sampleInterval) {
super(sampleInterval);
BUSY_TIME = (int) (mSampleInterval * 1.2f);
}
@Override
public void start() {
super.start();
reset();
}
/**
* Get cpu rate information
*
* @return string show cpu rate information
*/
public String getCpuRateInfo() {
StringBuilder sb = new StringBuilder();
synchronized (mCpuInfoEntries) {
for (Map.Entry<Long, String> entry : mCpuInfoEntries.entrySet()) {
long time = entry.getKey();
sb.append(BlockInfo.TIME_FORMATTER.format(time))
.append(' ')
.append(entry.getValue())
.append(BlockInfo.SEPARATOR);
}
}
return sb.toString();
}
public boolean isCpuBusy(long start, long end) {
if (end - start > mSampleInterval) {
long s = start - mSampleInterval;
long e = start + mSampleInterval;
long last = 0;
synchronized (mCpuInfoEntries) {
for (Map.Entry<Long, String> entry : mCpuInfoEntries.entrySet()) {
long time = entry.getKey();
if (s < time && time < e) {
if (last != 0 && time - last > BUSY_TIME) {
return true;
}
last = time;
}
}
}
}
return false;
}
@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 = "";
}
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);
}
}
}
private void reset() {
mUserLast = 0;
mSystemLast = 0;
mIdleLast = 0;
mIoWaitLast = 0;
mTotalLast = 0;
mAppCpuTimeLast = 0;
}
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;
}
}
做的工作差不多
每隔一定时间特定文件读取一下CPU信息,并保存到LinkedHashMap。
总结:
代码还是很简单的。
-
根据消息执行前和执行后的时间差,判断是否卡顿。
-
执行消息的时候,开启子线程打印主线程的堆栈信息和CPU信息并使用LinkedHashMap保存。
-
并通过DisplayActivity显示卡顿详情。