4章 性能平台GodEye源码分析-监控模块
4. 监控模块
4.1 Pageload
Pageload指的是监听应用Activity和Fragment的生命周期,通过记录当前的生命周期追踪用户的页面跳转行为
源码分析
1、启动Pageload的监控
Pageload的监控通过系统回调的生命周期,通过ActivityLifecycleCallbacks.work()
注册系统的生命周期,进而通过回调获取Pageload信息
public class Pageload extends ProduceableSubject<PageLifecycleEventInfo> implements Install<PageloadConfig> {
private static final String PAGELOAD_HANDLER = "godeye-pageload";
private ActivityLifecycleCallbacks mActivityLifecycleCallbacks;
private PageloadConfig mConfig;
private boolean mInstalled = false;
@Override
public synchronized boolean install(PageloadConfig config) {
if (mInstalled) {
L.d("Pageload already installed, ignore.");
return true;
}
this.mConfig = config;
PageInfoProvider pageInfoProvider = new DefaultPageInfoProvider();
try {
pageInfoProvider = (PageInfoProvider) Class.forName(this.mConfig.pageInfoProvider()).newInstance();
} catch (Throwable e) {
L.e("Pageload install warning, can not find pageload provider class. use DefaultPageInfoProvider:" + e);
}
Handler handler = ThreadUtil.createIfNotExistHandler(PAGELOAD_HANDLER);
this.mActivityLifecycleCallbacks = new ActivityLifecycleCallbacks(new PageLifecycleRecords(), pageInfoProvider, this, handler);
this.mActivityLifecycleCallbacks.work();
this.mInstalled = true;
L.d("Pageload installed.");
return true;
}
}
2、采集Pageload信息
通过Application注册当前的生命周期回调,实现ActivityLifecycleCallbacks
接口,当App启动或跳转页面时,会回调对应的生命周期
@Override
public void work() {
GodEye.instance().getApplication().registerActivityLifecycleCallbacks(this);
}
在onActivityCreated
回调中,大于AndroidO则可以注册当前界面的Fragment
回调,或者是FragmentActivity
也能注册
@Override
public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.getFragmentManager().registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks(ActivityLifecycleCallbacks.this), true);
}
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacksV4(ActivityLifecycleCallbacks.this), true);
}
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_CREATE, false);
}
在onActivityStarted
回调中,在这里会处理计算当前界面的OnDraw时间,如果处理过则加入到mStartedActivity
集合中
@Override
public void onActivityStarted(final Activity activity) {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_START, false);
if (!mStartedActivity.contains(activity)) {
mStartedActivity.add(activity);
ViewUtil.measureActivityDidDraw(activity, () -> {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_DRAW, false);
});
}
}
处理onDraw耗时的原理是:
- 通过在ActivityStart的时候监听通知
DecorView.getViewTreeObserver().addOnDrawListener
- 通过
postTraversalFinishCallBack
执行Choreographer
的postCallback()
方法,等待系统的Vsync回调 - 在Vsync回调中,如果已经执行过onDraw回调则直接回调到最外层,如果未执行过onDraw回调则重试,最多3次
//ViewUtil.java
public static void measureActivityDidDraw(final Activity activity, final OnDrawCallback onDrawCallback) {
measurePageDidDraw(activity.getWindow().getDecorView(), onDrawCallback);
}
private static void measurePageDidDraw(final View view, final OnDrawCallback onDrawCallback) {
PageDrawMonitor.newInstance(view, onDrawCallback).listen();
}
public void listen() {
ViewTreeObserver.OnDrawListener onDrawListener = () -> isDraw = true;
view.getViewTreeObserver().addOnDrawListener(onDrawListener); //监听通知
runOnDrawEnd(view, onDrawListener, 3, onDrawCallback); //执行回调
}
private void runOnDrawEnd(View view, ViewTreeObserver.OnDrawListener onDrawListener, int maxPostTimes, @NonNull ViewUtil.OnDrawCallback onDrawCallback) {
if (view == null || onDrawListener == null) {
return;
}
maxPostTimes --;
int finalMaxPostTimes = maxPostTimes;
postTraversalFinishCallBack(() -> {
if (!isDraw && finalMaxPostTimes > 0) { //isDraw没被回调,则重试
runOnDrawEnd(view, onDrawListener, finalMaxPostTimes, onDrawCallback);
} else { //isDraw被回调,移除并回调到最外层
view.getViewTreeObserver().removeOnDrawListener(onDrawListener);
onDrawCallback.didDraw();
}
});
}
private void postTraversalFinishCallBack(OnTraversalFinishListener onTraversalFinishListener) {
if (choreographerMethod == null) {
return;
}
try {
choreographerMethod.invoke(ChoreographerInjecor.getChoreographerProvider().getChoreographer(), CALLBACK_COMMIT, (Runnable) () -> {
if (onTraversalFinishListener != null) {
onTraversalFinishListener.onFinish();
}
}, FRAME_CALLBACK_TOKEN);
} catch (Throwable throwable) {
L.w(throwable.getMessage());
}
}
同样的在其他回调中,可以看到最终都会回调onActivityLifecycleEvent()
@Override
public void onActivityResumed(final Activity activity) {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_RESUME, false);
}
@Override
public void onActivityPaused(final Activity activity) {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_PAUSE, false);
}
@Override
public void onActivityStopped(final Activity activity) {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_STOP, false);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(final Activity activity) {
onActivityLifecycleEvent(activity, ActivityLifecycleEvent.ON_DESTROY, false);
mStartedActivity.remove(activity);
}
在onActivityLifecycleEvent()
中,对当前的生命周期做记录和采集,其中步骤分为
-
new PageInfo()
:初始化数据对象 -
mPageLifecycleRecords.isExistEvent()
:判断当前生命周期对象是否已经记录下来 -
mPageLifecycleRecords.addLifecycleEvent()
:将当前生命周期对象进行记录 -
mProducer.produce()
:采集当前的生命周期对象信息
private void onActivityLifecycleEvent(Activity activity, LifecycleEvent e, boolean canNotRepeat) {
final long time = System.currentTimeMillis();
mHandler.post(() -> {
PageInfo<Activity> pageInfo = new PageInfo<>(activity, mPageInfoProvider.getInfoByActivity(activity));
if (canNotRepeat && mPageLifecycleRecords.isExistEvent(pageInfo, e)) {
return;
}
PageLifecycleEventWithTime<Activity> lifecycleEvent = mPageLifecycleRecords.addLifecycleEvent(pageInfo, e, time);
if (lifecycleEvent != null) {
mProducer.produce(new PageLifecycleEventInfo<>(pageInfo, lifecycleEvent, mPageLifecycleRecords.getLifecycleEventsByPageInfo(pageInfo)));
}
});
}
在PageInfo
对象初始化的时候,就已经把对应的字段赋值成对应的值,其中参数extraInfo
就是从xml配置文件中读取的参数值,作为额外的标注信息
public PageInfo(T page, Map<String, String> extraInfo) {
if (page instanceof Activity) {
this.pageType = PageType.ACTIVITY;
} else if (page instanceof Fragment || page instanceof android.app.Fragment) {
this.pageType = PageType.FRAGMENT;
} else {
this.pageType = PageType.UNKNOWN;
}
this.pageClassName = page.getClass().getName();
this.pageHashCode = page.hashCode();
this.extraInfo = extraInfo;
}
在记录和采集之前通过isExistEvent()
判断下是否已经处理过当前的生命周期对象,如果判断是处理过的对象则返回true
synchronized boolean isExistEvent(PageInfo pageInfo, LifecycleEvent lifecycleEvent) {
List<PageLifecycleEventWithTime> pageLifecycleEventWithTimes = getLifecycleEventsByPageInfo(pageInfo);
if (pageLifecycleEventWithTimes != null && !pageLifecycleEventWithTimes.isEmpty()) {
for (PageLifecycleEventWithTime pageLifecycleEventWithTime : pageLifecycleEventWithTimes) {
if (pageLifecycleEventWithTime.lifecycleEvent == lifecycleEvent) {
return true;
}
}
}
return false;
}
判断未处理的事件,则会通过mPageLifecycleRecords.addLifecycleEvent()
将当前处理过的记录存储起来
- 如果是非系统的生命周期则直接新增到集合中
- 如果是系统的生命周期,则访问
mTopLifecycleMethodEventForPages
是否有对象,如果没有则直接新增到集合中 -
mTopLifecycleMethodEventForPages
是属于MethodCanary的过程中加入的监控对象,正常情况会为null
synchronized @Nullable
<T> PageLifecycleEventWithTime<T> addLifecycleEvent(PageInfo<T> pageInfo, LifecycleEvent lifecycleEvent, long time) {
if (!isSystemLifecycleEvent(lifecycleEvent)) {
return addEvent(pageInfo, new PageLifecycleEventWithTime<>(pageInfo, lifecycleEvent, time, time));
}
LifecycleMethodEventWithTime currentTop = mTopLifecycleMethodEventForPages.get(pageInfo);
PageLifecycleEventWithTime<T> tmp = null;
if (currentTop == null) {
tmp = new PageLifecycleEventWithTime<>(pageInfo, lifecycleEvent, time, time);
} else if (!PageLifecycleMethodEventTypes.isMatch(lifecycleEvent, currentTop.lifecycleEvent)) {
tmp = new PageLifecycleEventWithTime<>(pageInfo, lifecycleEvent, time, time);
} else if (currentTop.methodStartTime > 0 && currentTop.methodEndTime > 0) {
tmp = new PageLifecycleEventWithTime<>(pageInfo, lifecycleEvent, currentTop.methodStartTime, time);
}
if (tmp != null) {
mTopLifecycleMethodEventForPages.remove(pageInfo);
return addEvent(pageInfo, tmp);
} else {
currentTop.lifecycleTime = time;
return null;
}
}
addEvent()
会将当前的事件,添加到当前声明过的数据结构中,其中
- mPageLifecycleEventIndexing:保存着所有页面,其页面对应的生命周期事件的索引列表
- mLifecycleEvents:保存所有页面的生命周期,可以通过
mPageLifecycleEventIndexing
中的索引列表来这里取对应的值 - mTopLifecycleMethodEventForPages:保存着所有页面,当前的*生命周期事件
private Map<PageInfo, List<Integer>> mPageLifecycleEventIndexing = new HashMap<>();
private List<PageLifecycleEventWithTime> mLifecycleEvents = new ArrayList<>();
private Map<PageInfo, LifecycleMethodEventWithTime> mTopLifecycleMethodEventForPages = new HashMap<>();
private <T> PageLifecycleEventWithTime<T> addEvent(PageInfo<T> pageInfo, PageLifecycleEventWithTime<T> pageLifecycleEventLine) {
//每次新增都会增加事件到当前集合,该集合维护所有页面的所有生命周期事件
mLifecycleEvents.add(pageLifecycleEventLine);
//每个页面维护着所有生命周期在mLifecycleEvents中的索引
List<Integer> pageEventIndexingList = mPageLifecycleEventIndexing.get(pageInfo);
if (pageEventIndexingList == null) {
pageEventIndexingList = new ArrayList<>();
//如果当前界面还未保存过,则新增个界面信息,mPageLifecycleEventIndexing保存着每个界面的所有生命周期的参数
mPageLifecycleEventIndexing.put(pageInfo, pageEventIndexingList);
}
pageEventIndexingList.add(mLifecycleEvents.size() - 1);
return pageLifecycleEventLine;
}
看完addEvent()
方法后,知道了其保存的数据结构,那么getLifecycleEventsByPageInfo()
中,相当于去解析当前的数据结构,获取想要的数据结果
- 获取当前生命周期的index位置
- 通过index去获取当前页面的生命周期对象
- 将属于当前页面的生命周期对象加入到新的集合中返回
synchronized List<PageLifecycleEventWithTime> getLifecycleEventsByPageInfo(PageInfo pageInfo) {
List<Integer> pageEventIndexingList = mPageLifecycleEventIndexing.get(pageInfo); //获取当前页面的生命周期,在整个集合中的索引位置
if (pageEventIndexingList == null) {
return new ArrayList<>();
}
List<PageLifecycleEventWithTime> pageLifecycleEventWithTimes = new ArrayList<>(pageEventIndexingList.size());
for (int index : pageEventIndexingList) {
pageLifecycleEventWithTimes.add(mLifecycleEvents.get(index)); //通过索引位置获取整个集合属于当前页面的生命周期对象
}
return pageLifecycleEventWithTimes;
}
3、总结
Activity的生命周期的获取和采集作者做了额外的非系统生命周期,会去计算界面draw的时间,对于Fragment的生命周期的获取和采集,和Activity的原理相同
4.2 ViewCanary
ViewCanary会通过代码去扫描当前界面的层级,遍历并采集所有View的属性和信息
源码分析
1、启动ViewCanary的监控
ViewCanary通过系统回调的生命周期,通过ViewCanaryInternal
获取ViewCanary信息
public class ViewCanary extends ProduceableSubject<ViewIssueInfo> implements Install<ViewCanaryConfig> {
private ViewCanaryConfig config;
private boolean mInstalled = false;
private ViewCanaryInternal mViewCanaryInternal;
@Override
public synchronized boolean install(ViewCanaryConfig config) {
if (mInstalled) {
L.d("ViewCanary already installed, ignore.");
return true;
}
this.config = config;
mViewCanaryInternal = new ViewCanaryInternal();
mViewCanaryInternal.start(this, config());
mInstalled = true;
L.d("ViewCanary installed.");
return true;
}
}
2、采集ViewCanary信息
- 通过
mViewCanaryInternal.start()
调用registerActivityLifecycleCallbacks()
注册Activity的生命周期广播通知 - 在每个Activity启动的时候,在
onActivityCreated
周期中,记录扫过的界面,在onActivityDestroyed
周期中,移除扫过的界面 - 在每个Activity启动的时候,在
onActivityResumed
周期中,开启界面的扫描,在onActivityPaused
周期中,停止界面的扫描
void start(ViewCanary viewCanary, ViewCanaryConfig config) {
Handler handler = ThreadUtil.createIfNotExistHandler(VIEW_CANARY_HANDLER);
callbacks = new SimpleActivityLifecycleCallbacks() {
private Map<Activity, ViewTreeObserver.OnGlobalLayoutListener> mOnGlobalLayoutListenerMap = new HashMap<>();
private Map<Activity, List<List<ViewIdWithSize>>> mRecentLayoutListRecords = new HashMap<>();
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
mRecentLayoutListRecords.put(activity, new ArrayList<>());
}
@Override
public void onActivityDestroyed(Activity activity) {
super.onActivityDestroyed(activity);
mRecentLayoutListRecords.remove(activity);
}
@Override
public void onActivityResumed(Activity activity) {
super.onActivityResumed(activity);
ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
Runnable callback = inspectInner(new WeakReference<>(activity), viewCanary, config, mRecentLayoutListRecords);
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = () -> {
handler.removeCallbacks(callback);
handler.postDelayed(callback, INSPECT_DELAY_TIME_MILLIS);
};
mOnGlobalLayoutListenerMap.put(activity, onGlobalLayoutListener);
parent.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
@Override
public void onActivityPaused(Activity activity) {
super.onActivityPaused(activity);
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = mOnGlobalLayoutListenerMap.remove(activity);
ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
if (onGlobalLayoutListener != null) {
parent.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
};
GodEye.instance().getApplication().registerActivityLifecycleCallbacks(callbacks);
}
在界面启动时,回调onActivityResumed
中
- 声明界面扫描任务的
callback
,通过inspectInner
类去封装逻辑 - 当界面绘制完成后,
GlobalLayoutListener
会回调,将callback
分发给Handler
去执行
@Override
public void onActivityResumed(Activity activity) {
super.onActivityResumed(activity);
ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
Runnable callback = inspectInner(new WeakReference<>(activity), viewCanary, config, mRecentLayoutListRecords);
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = () -> {
handler.removeCallbacks(callback);
handler.postDelayed(callback, INSPECT_DELAY_TIME_MILLIS);
};
mOnGlobalLayoutListenerMap.put(activity, onGlobalLayoutListener);
parent.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
Handler会去执行callback
的run()
回调,真正执行界面的扫描工作,其原理是:
- 通过rootview获取所有子View和子View的层级数,加入到Map中
- 获取每个View的宽高并记录下来,由于采集显示的页面需要展示视图位置和大小
- 检查并统计每个View的过度位置信息
- 构建问题View对象,将所有信息赋值后,发送出去
@VisibleForTesting
Runnable inspectInner(WeakReference<Activity> activity, ViewCanary viewCanary, ViewCanaryConfig config, Map<Activity, List<List<ViewIdWithSize>>> recentLayoutListRecords) {
return () -> {
try {
Activity p = activity.get();
if (p != null) {
ViewGroup parent = (ViewGroup) p.getWindow().getDecorView();
inspect(p, parent, viewCanary, config, recentLayoutListRecords);
}
} catch (Throwable e) {
L.e(e);
}
};
}
private void inspect(Activity activity, ViewGroup root, ViewCanary viewCanary, ViewCanaryConfig config, Map<Activity, List<List<ViewIdWithSize>>> recentLayoutListRecords) {
//1、将所有View捕捉下来,待检查是否过度绘制
long startTime = System.currentTimeMillis();
List<List<ViewIdWithSize>> records = recentLayoutListRecords.get(activity);
if (records == null) { //如果已经检查过当前Activity的,则直接忽略
return;
}
Map<View, Integer> depthMap = new HashMap<>();
List<View> allViews = new ArrayList<>();
allViews.add(root);
recursiveLoopChildren(root, depthMap, allViews); //循环遍历所有View,并记录所有View的层级数到depthMap
List<ViewIdWithSize> layoutEigenvalue = getLayoutEigenvalue(root, allViews); //获取所有View的宽高
boolean allNotSimilar = allNotSimilarByMeasureDistanceLayoutEigenvalueWithRecords(records, layoutEigenvalue);
//2、检查过度绘制的视图信息
Map<Rect, Set<Object>> overDrawMap = checkOverDraw(allViews);
//3、构建问题View的对象
ViewIssueInfo info = new ViewIssueInfo();
int[] resolution = getResolution();
info.activityName = activity.getClass().getName(); //记录Activity参数
info.timestamp = System.currentTimeMillis(); //记录时间戳参数
info.maxDepth = config.maxDepth(); //记录max层级参数
info.screenWidth = resolution[0]; //记录获取屏幕信息
info.screenHeight = resolution[1]; //记录获取屏幕信息
for (Map.Entry<View, Integer> entry : depthMap.entrySet()) { //记录所有View参数
info.views.add(getViewInfo(entry.getKey(), entry.getValue()));
}
for (Map.Entry<Rect, Set<Object>> entry : overDrawMap.entrySet()) {
ViewIssueInfo.OverDrawArea overDrawArea = new ViewIssueInfo.OverDrawArea();
overDrawArea.rect = entry.getKey();
overDrawArea.overDrawTimes = getOverDrawTimes(entry.getValue()) - 1;
info.overDrawAreas.add(overDrawArea); //记录过度绘制层数参数对象
}
records.add(layoutEigenvalue);
viewCanary.produce(info);
L.d("ViewCanary inspect %s complete, cost %sms.", activity.getClass().getSimpleName(), (System.currentTimeMillis() - startTime));
}
通过checkOverDraw
实现检查过度绘制的区域,并记录下来,其原理是
- 通过两次循环遍历当前View和所有View相交部分的区域
- 通过两次循环遍历当前相交部分的区域视图,和所有相交部分的区域视图,再做一层相交处理
- 通过这两个操作后,就算出了相交一次和相交两次后的所有视图区域信息
private Map<Rect, Set<Object>> checkOverDraw(List<View> allViews) {
Map<Rect, Set<Object>> map = new HashMap<>();
//两次循环遍历当前View和所有View
for (int i = 0; i < allViews.size(); i++) {
View view = allViews.get(i);
for (int j = i + 1; j < allViews.size(); j++) {
View other = allViews.get(j);
int viewLayer = ViewBgUtil.getLayer(view);
int otherLayer = ViewBgUtil.getLayer(other);
if (view == other || viewLayer == 0 || otherLayer == 0) {
continue;
}
Rect r1 = new Rect();
Rect r2 = new Rect();
getViewRect(view, r1);
getViewRect(other, r2);
//如果没相交则继续,相交则下一步
if (!Rect.intersects(r1, r2)) {
continue;
}
//计算相交部分的区域
Rect overDraw = calculateOverDraw(r1, r2);
Set<Object> set;
if (map.containsKey(overDraw)) {
set = map.get(overDraw);
} else {
set = new HashSet<>();
}
//保存相交的区域
set.add(view);
set.add(other);
map.put(overDraw, set);
}
}
Map<Rect, Set<Object>> rest = new HashMap<>();
//两次循环遍历当前相交部分的区域视图,和所有相交部分的区域视图
for (Map.Entry<Rect, Set<Object>> entry : map.entrySet()) {
Rect rect = entry.getKey();
for (Map.Entry<Rect, Set<Object>> otherEntry : map.entrySet()) {
Rect otherRect = otherEntry.getKey();
//如果没相交则继续,相交则下一步
if (rect == otherRect || !Rect.intersects(rect, otherRect)) {
continue;
}
Rect overDraw = calculateOverDraw(rect, otherRect);
if (map.containsKey(overDraw)) {
continue;
}
Set<Object> set;
if (rest.containsKey(overDraw)) {
set = rest.get(overDraw);
} else {
set = new HashSet<>();
}
//保存相交的区域
set.add(otherRect);
rest.put(overDraw, set);
}
}
map.putAll(rest);
return map;
}
如何知道相交的部分,则通过calculateOverDraw
,画个相交的矩形,对着代码看就知道什么意思了
private Rect calculateOverDraw(Rect r1, Rect r2) {
Rect overDrawArea = new Rect();
overDrawArea.left = Math.max(r1.left, r2.left);
overDrawArea.right = Math.min(r1.right, r2.right);
overDrawArea.bottom = Math.min(r1.bottom, r2.bottom);
overDrawArea.top = Math.max(r1.top, r2.top);
return overDrawArea;
}
3、总结
ViewCanary的思维比较简单,计算方式比较多,只要有同样的思路,用代码表达出来就可以了,简单的说就是遍历View树,再结合View的属性来完成视图的计算工作
4.3 ImageCanary
ImageCanary会通过代码去扫描当前界面的所有图片,通过自定义问题图片的规则,过滤出有问题的图片
源码分析
1、启动ImageCanary的监控
ImageCanary通过系统回调的生命周期,通过ImageCanaryInternal
获取ImageCanary信息
public class ImageCanary extends ProduceableSubject<ImageIssue> implements Install<ImageCanaryConfig> {
private boolean mInstalled = false;
private ImageCanaryConfig mConfig;
private ImageCanaryInternal mImageCanaryInternal;
@Override
public synchronized boolean install(ImageCanaryConfig config) {
if (mInstalled) {
L.d("ImageCanary already installed, ignore.");
return true;
}
mConfig = config;
ImageCanaryConfigProvider imageCanaryConfigProvider = new DefaultImageCanaryConfigProvider();
try {
imageCanaryConfigProvider = (ImageCanaryConfigProvider) Class.forName(mConfig.getImageCanaryConfigProvider()).newInstance();
} catch (Throwable e) {
L.e("ImageCanary install warning, can not find imageCanaryConfigProvider class. use DefaultImageCanaryConfigProvider:" + e);
}
mImageCanaryInternal = new ImageCanaryInternal(imageCanaryConfigProvider);
mImageCanaryInternal.start(GodEye.instance().getApplication(), this);
mInstalled = true;
L.d("ImageCanary installed.");
return true;
}
}
其中DefaultImageCanaryConfigProvider
属于问题图片的规则定义,其规则如下
- Bitmap质量过大:图片的尺寸大于ImageView的尺寸的1.5倍
- Bitmap质量过小:图片的尺寸的2倍小于ImageView的尺寸
public class DefaultImageCanaryConfigProvider implements ImageCanaryConfigProvider {
@Override
public boolean isBitmapQualityTooHigh(int bitmapWidth, int bitmapHeight, int imageViewWidth, int imageViewHeight) {
return bitmapWidth * bitmapHeight > imageViewWidth * imageViewHeight * 1.5;
}
@Override
public boolean isBitmapQualityTooLow(int bitmapWidth, int bitmapHeight, int imageViewWidth, int imageViewHeight) {
return bitmapWidth * bitmapHeight * 2 < imageViewWidth * imageViewHeight;
}
}
2、采集ImageCanary信息
- 通过
ImageCanaryInternal.start()
调用registerActivityLifecycleCallbacks()
注册Activity的生命周期广播通知 - 在每个Activity启动的时候,在
onActivityResumed
周期中,通过监听addOnDrawListener
扫描界面上所有问题图片 - 在每个Activity启动的时候,在
onActivityPaused
周期中,移除监听removeOnDrawListener
void start(Application application, ImageCanary imageCanaryEngine) {
Handler handler = ThreadUtil.createIfNotExistHandler(IMAGE_CANARY_HANDLER);
callbacks = new SimpleActivityLifecycleCallbacks() {
private Map<Activity, ViewTreeObserver.OnDrawListener> mOnDrawListenerMap = new HashMap<>();
private Set<ImageIssue> mImageIssues = new HashSet<>();
@Override
public void onActivityResumed(Activity activity) {
super.onActivityResumed(activity);
ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
Runnable callback = inspectInner(new WeakReference<>(activity), imageCanaryEngine, mImageIssues);
ViewTreeObserver.OnDrawListener onDrawListener = () -> {
if (handler != null) {
handler.removeCallbacks(callback);
handler.postDelayed(callback, 300);
}
};
mOnDrawListenerMap.put(activity, onDrawListener);
parent.getViewTreeObserver().addOnDrawListener(onDrawListener);
}
@Override
public void onActivityPaused(Activity activity) {
super.onActivityPaused(activity);
ViewTreeObserver.OnDrawListener onDrawListener = mOnDrawListenerMap.remove(activity);
ViewGroup parent = (ViewGroup) activity.getWindow().getDecorView();
if (onDrawListener != null) {
parent.getViewTreeObserver().removeOnDrawListener(onDrawListener);
}
}
};
application.registerActivityLifecycleCallbacks(callbacks);
}
扫描问题图片逻辑跟ViewCanary基本一致
- 当
ViewTreeObserver.OnDrawListener
回调的时候,会将callback
交给Handler
去处理 -
Handler
启动callback
的run()
,也就是inspectInner
里面,执行recursiveLoopChildren
- 获取
DecorView
遍历整个View树的Bitmap
和Drawable
匹配问题图片的规则 - 将问题图片封装好的对象,发送出去
Runnable inspectInner(WeakReference<Activity> activity, ImageCanary imageCanaryEngine, Set<ImageIssue> imageIssues) {
return () -> {
try {
Activity p = activity.get();
if (p != null) {
ViewGroup parent = (ViewGroup) p.getWindow().getDecorView();
recursiveLoopChildren(p, parent, imageCanaryEngine, imageIssues);
}
} catch (Throwable e) {
L.e(e);
}
};
}
private void recursiveLoopChildren(Activity activity, ViewGroup parent, ImageCanary imageCanaryEngine, Set<ImageIssue> imageIssues) {
ViewUtil.getChildren(parent, view -> false, view -> {
List<BitmapInfo> bitmapInfos = mBitmapInfoAnalyzer.analyze(view);
for (BitmapInfo bitmapInfo : bitmapInfos) {
if (bitmapInfo.isValid()) {
ImageIssue imageIssue = new ImageIssue();
imageIssue.bitmapHeight = bitmapInfo.bitmapHeight;
imageIssue.bitmapWidth = bitmapInfo.bitmapWidth;
imageIssue.imageViewHashCode = view.hashCode();
imageIssue.imageViewWidth = view.getWidth();
imageIssue.imageViewHeight = view.getHeight();
imageIssue.activityClassName = activity.getClass().getName();
imageIssue.activityHashCode = activity.hashCode();
imageIssue.timestamp = System.currentTimeMillis();
if (view.getVisibility() != View.VISIBLE) {
imageIssue.issueType = ImageIssue.IssueType.INVISIBLE_BUT_MEMORY_OCCUPIED;
} else if (mImageCanaryConfigProvider.isBitmapQualityTooHigh(bitmapInfo.bitmapWidth, bitmapInfo.bitmapHeight, view.getWidth(), view.getHeight())) {
imageIssue.issueType = ImageIssue.IssueType.BITMAP_QUALITY_TOO_HIGH;
} else if (mImageCanaryConfigProvider.isBitmapQualityTooLow(bitmapInfo.bitmapWidth, bitmapInfo.bitmapHeight, view.getWidth(), view.getHeight())) {
imageIssue.issueType = ImageIssue.IssueType.BITMAP_QUALITY_TOO_LOW;
} else {
imageIssue.issueType = ImageIssue.IssueType.NONE;
}
if (imageIssue.issueType != ImageIssue.IssueType.NONE && !imageIssues.contains(imageIssue)) {
imageIssues.add(new ImageIssue(imageIssue));
imageIssue.imageSrcBase64 = ImageUtil.convertToBase64(bitmapInfo.bitmap.get(), 200, 200);
imageCanaryEngine.produce(imageIssue);
}
}
}
});
}
通过ViewUtil.getChildren
遍历整个子树图,将每个View交给DefaultBitmapInfoAnalyzer
进行分析,匹配Bitmap
和Drawable
public class DefaultBitmapInfoAnalyzer implements BitmapInfoAnalyzer {
@Override
public List<BitmapInfo> analyze(View view) {
BitmapInfo ivBitMapInfo = null;
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
ivBitMapInfo = new BitmapInfo();
ivBitMapInfo.bitmapWidth = bitmap.getWidth();
ivBitMapInfo.bitmapHeight = bitmap.getHeight();
ivBitMapInfo.bitmap = new WeakReference<>(bitmap);
}
}
BitmapInfo vBitMapInfo;
Drawable drawable = view.getBackground();
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
vBitMapInfo = new BitmapInfo();
vBitMapInfo.bitmapWidth = bitmap.getWidth();
vBitMapInfo.bitmapHeight = bitmap.getHeight();
vBitMapInfo.bitmap = new WeakReference<>(bitmap);
if (ivBitMapInfo != null) {
return Arrays.asList(ivBitMapInfo, vBitMapInfo);
}
return Arrays.asList(vBitMapInfo);
}
if (ivBitMapInfo != null) {
return Arrays.asList(ivBitMapInfo);
}
return Collections.emptyList();
}
3、总结
ImageCanary原理并不难,对View树的遍历,思想和ViewCanary基本一致
4.4 MethodCanary
MethodCanary用的是作者的另一个框架,通过调用开始和结束,采集当前时间段内线程中执行的函数和时间线
源码分析
1、启动MethodCanary的监控
MethodCanary的启动没做任何事情,因为其采集是需要手动调用才可以
public class MethodCanary extends ProduceableSubject<MethodsRecordInfo> implements Install<MethodCanaryConfig> {
private boolean mInstalled = false;
private MethodCanaryConfig mMethodCanaryContext;
@Override
public synchronized boolean install(final MethodCanaryConfig methodCanaryContext) {
if (this.mInstalled) {
L.d("MethodCanary already installed, ignore.");
return true;
}
this.mMethodCanaryContext = methodCanaryContext;
this.mInstalled = true;
L.d("MethodCanary installed.");
return true;
}
}
2、采集MethodCanary信息
采集的过程使用MethodCanary提供的Api,需要手动调用启动和结束,结束后将返回的方法信息发送出去
public synchronized void startMonitor(String tag) {
try {
if (!isInstalled()) {
L.d("MethodCanary start monitor fail, not installed.");
return;
}
cn.hikyson.methodcanary.lib.MethodCanary.get().startMethodTracing(tag);
L.d("MethodCanary start monitor success.");
} catch (Exception e) {
L.d("MethodCanary start monitor fail:" + e);
}
}
public synchronized void stopMonitor(String tag) {
try {
if (!isInstalled()) {
L.d("MethodCanary stop monitor fail, not installed.");
return;
}
cn.hikyson.methodcanary.lib.MethodCanary.get().stopMethodTracing(tag
, new cn.hikyson.methodcanary.lib.MethodCanaryConfig(this.mMethodCanaryContext.lowCostMethodThresholdMillis()), (sessionTag, startMillis, stopMillis, methodEventMap) -> {
long start0 = System.currentTimeMillis();
MethodsRecordInfo methodsRecordInfo = MethodCanaryConverter.convertToMethodsRecordInfo(startMillis, stopMillis, methodEventMap);
// recordToFile(methodEventMap, methodsRecordInfo);
long start1 = System.currentTimeMillis();
MethodCanaryConverter.filter(methodsRecordInfo, this.mMethodCanaryContext);
long end = System.currentTimeMillis();
L.d(String.format("MethodCanary output success! cost %s ms, filter cost %s ms", end - start0, end - start1));
produce(methodsRecordInfo);
});
L.d("MethodCanary stopped monitor and output processing...");
} catch (Exception e) {
L.d("MethodCanary stop monitor fail:" + e);
}
}
作者也是通过Web点击开始和结束操作当前的MethodCanary
public class WebSocketMethodCanaryProcessor implements WebSocketProcessor {
@Override
public void process(WebSocket webSocket, JSONObject msgJSONObject) {
try {
if ("start".equals(msgJSONObject.optString("payload"))) {
GodEyeHelper.startMethodCanaryRecording("AndroidGodEye-Monitor-Tag");
} else if ("stop".equals(msgJSONObject.optString("payload"))) {
GodEyeHelper.stopMethodCanaryRecording("AndroidGodEye-Monitor-Tag");
}
webSocket.send(new ServerMessage("methodCanaryMonitorState", Collections.singletonMap("isRunning", GodEyeHelper.isMethodCanaryRecording("AndroidGodEye-Monitor-Tag"))).toString());
} catch (UninstallException e) {
L.e(String.valueOf(e));
}
}
}
3、MethodCanary源码分析
原理待后面补齐
4、总结
MethodCanary主要是依赖于作者的第三方库,原理后续推出
4.5 Sm(BlockCanary)
Sm主要是分析当前主线程是否阻塞,阻塞的话其堆栈又是哪些。Sm主要是利用第三方框架BlockCanary的原理,由于Handler每次执行主线程都会通过Printer输出当前主线程的调用时间,系统Api提供设置Printer的方法,通过设置自定义的Printer时,Printer会回调当前主线程执行的时间,通过对时间长的过滤认为是阻塞
源码分析
1、启动Sm的监控
Sm的监控通过SmCore
去启动,读取xml的参数设置最小阻塞时间shortBlockThreshold
和最大阻塞时间longBlockThreshold
,通过mBlockCore.setBlockListener
设置回调
- 当满足短时间阻塞条件时,则回调
onShortBlock()
后并输出数据 - 当满足长时间阻塞条件时,则回调
onLongBlock()
后并输出数据
public final class Sm extends ProduceableSubject<BlockInfo> implements Install<SmConfig> {
private SmCore mBlockCore;
private SmConfig mSmRealConfig;
private SmConfig mSmConfig;
private boolean mInstalled = false;
@Override
public synchronized boolean install(SmConfig config) {
if (mInstalled) {
L.d("Sm already installed, ignore.");
return true;
}
this.mInstalled = true;
this.mSmConfig = config;
this.mSmRealConfig = wrapRealConfig(config);
this.mBlockCore = new SmCore(GodEye.instance().getApplication(), this.mSmRealConfig.longBlockThreshold(), this.mSmRealConfig.shortBlockThreshold(), this.mSmRealConfig.dumpInterval());
this.mBlockCore.setBlockListener(new BlockListener() {
@Override
public void onStart(Context context) {
}
@Override
public void onStop(Context context) {
}
@WorkerThread
@Override
public void onShortBlock(Context context, ShortBlockInfo shortBlockInfo) {
ThreadUtil.ensureWorkThread("Sm onShortBlock");
produce(new BlockInfo(shortBlockInfo));
}
@WorkerThread
@Override
public void onLongBlock(Context context, LongBlockInfo blockInfo) {
ThreadUtil.ensureWorkThread("Sm onLongBlock");
produce(new BlockInfo(blockInfo));
}
});
mBlockCore.install();
L.d("Sm installed");
return true;
}
}
通过mBlockCore.install()
将参数mMonitor
设置成Printer
public void install() {
ThreadUtil.createIfNotExistHandler(AbstractSampler.SM_DO_DUMP);
Looper.getMainLooper().setMessageLogging(mMonitor);
}
2、采集Sm信息
通过SmCore
的LooperMonitor
监听Block回调
-
onEventStart
:当Looper事件开始的时候,启动StackSampler
采集堆栈 -
onEventEnd
:当Looper事件结束的时候,结束StackSampler
采集堆栈 -
onBlockEvent
:当Looper事件结束的时候,如果当前的主线程发生阻塞则回调
在onBlockEvent
中属于长时间阻塞,则输出
- 当前的Cpu信息
CpuInfo
- 当前线程的堆栈
StackTraceElement
- 当前内存信息
MemoryInfo
在onBlockEvent
中属于短时间阻塞,则输出
- 当前内存信息
MemoryInfo
public final class SmCore {
private Context mContext;
private LooperMonitor mMonitor;
private StackSampler stackSampler;
private CpuSampler cpuSampler;
private BlockListener mBlockListener;
private long mLongBlockThresholdMillis;
public SmCore(final Context context, long longBlockThresholdMillis, long shortBlockThresholdMillis, long dumpIntervalMillis) {
this.mContext = context;
this.mLongBlockThresholdMillis = longBlockThresholdMillis;
this.stackSampler = new StackSampler(
Looper.getMainLooper().getThread(), dumpIntervalMillis, getSampleDelay());
this.cpuSampler = new CpuSampler(dumpIntervalMillis, getSampleDelay());
this.mMonitor = new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onEventStart(long startTime) {
startDump();
}
@Override
public void onEventEnd(long endTime) {
stopDump();
}
@Override
public void onBlockEvent(final long blockTimeMillis, final long threadBlockTimeMillis, final boolean longBlock, final long eventStartTimeMilliis, final long eventEndTimeMillis, long longBlockThresholdMillis, long shortBlockThresholdMillis) {
ThreadUtil.computationScheduler().scheduleDirect(() -> {
if (AndroidDebug.isDebugging()) {// if debugging, then ignore
return;
}
if (longBlock) {
//如果是长卡顿,那么需要记录很多信息
final boolean cpuBusy = cpuSampler.isCpuBusy(eventStartTimeMilliis, eventEndTimeMillis);
//这里短卡顿基本是dump不到数据的,因为dump延时一般都会比短卡顿时间久
final List<CpuInfo> cpuInfos = cpuSampler.getCpuRateInfo(eventStartTimeMilliis, eventEndTimeMillis);
final Map<Long, List<StackTraceElement>> threadStackEntries = stackSampler.getThreadStackEntries(eventStartTimeMilliis, eventEndTimeMillis);
final MemoryInfo memoryInfo = new MemoryInfo(MemoryUtil.getAppHeapInfo(), MemoryUtil.getAppPssInfo(mContext), MemoryUtil.getRamInfo(mContext));
LongBlockInfo blockBaseinfo = new LongBlockInfo(eventStartTimeMilliis, eventEndTimeMillis, threadBlockTimeMillis,
blockTimeMillis, cpuBusy, cpuInfos, threadStackEntries, memoryInfo);
if (mBlockListener != null) {
mBlockListener.onLongBlock(context, blockBaseinfo);
}
} else {
final MemoryInfo memoryInfo = new MemoryInfo(MemoryUtil.getAppHeapInfo(), MemoryUtil.getAppPssInfo(mContext), MemoryUtil.getRamInfo(mContext));
ShortBlockInfo shortBlockInfo = new ShortBlockInfo(eventStartTimeMilliis, eventEndTimeMillis, threadBlockTimeMillis,
blockTimeMillis, memoryInfo);
if (mBlockListener != null) {
mBlockListener.onShortBlock(context, shortBlockInfo);
}
}
});
}
}, longBlockThresholdMillis, shortBlockThresholdMillis);
}
private void startDump() {
if (null != stackSampler) {
stackSampler.start();
}
if (null != cpuSampler) {
cpuSampler.start();
}
}
private void stopDump() {
if (null != stackSampler) {
stackSampler.stop();
}
if (null != cpuSampler) {
cpuSampler.stop();
}
}
}
LooperMonitor
继承Printer
,当Looper开始执行函数和结束执行函数时,都会执行Printer
的println(String x)
public class LooperMonitor implements Printer {
public static final String TAG = "LooperMonitor";
// 长卡顿的阀值
private long mLongBlockThresholdMillis;
// 短卡顿的阀值
private long mShortBlockThresholdMillis;
// 一次事件开始时间
private long mThisEventStartTime = 0;
// 一次事件开始时间(线程内)
private long mThisEventStartThreadTime = 0;
private BlockListener mBlockListener = null;
// 事件开始标记
private boolean mEventStart = false;
public interface BlockListener {
void onEventStart(long startTime);
void onEventEnd(long endTime);
/**
* 卡顿事件
*
* @param eventStartTimeMilliis 事件开始时间
* @param eventEndTimeMillis 事件结束时间
* @param blockTimeMillis 卡顿时间(事件处理时间)
* @param threadBlockTimeMillis 事件真实消耗时间
* @param longBlockThresholdMillis 长卡顿阀值标准
* @param shortBlockThresholdMillis 短卡顿阀值标准
*/
void onBlockEvent(long blockTimeMillis, long threadBlockTimeMillis, boolean longBlock,
long eventStartTimeMilliis, long eventEndTimeMillis, long longBlockThresholdMillis,
long shortBlockThresholdMillis);
}
LooperMonitor(BlockListener blockListener, long longBlockThresholdMillis, long shortBlockThresholdMillis) {
if (blockListener == null) {
throw new IllegalArgumentException("blockListener should not be null.");
}
mBlockListener = blockListener;
mLongBlockThresholdMillis = longBlockThresholdMillis;
mShortBlockThresholdMillis = shortBlockThresholdMillis;
}
/**
* 更新阀值配置
*
* @param shortBlockThresholdMillis
* @param longBlockThresholdMillis
*/
public void setBlockThreshold(long shortBlockThresholdMillis, long longBlockThresholdMillis) {
this.mShortBlockThresholdMillis = shortBlockThresholdMillis;
this.mLongBlockThresholdMillis = longBlockThresholdMillis;
}
@Override
public void println(String x) {
if (!mEventStart) {// 事件开始
mThisEventStartTime = System.currentTimeMillis();
mThisEventStartThreadTime = SystemClock.currentThreadTimeMillis();
mEventStart = true;
mBlockListener.onEventStart(mThisEventStartTime);
} else {// 事件结束
final long thisEventEndTime = System.currentTimeMillis();
final long thisEventThreadEndTime = SystemClock.currentThreadTimeMillis();
mEventStart = false;
long eventCostTime = thisEventEndTime - mThisEventStartTime;
long eventCostThreadTime = thisEventThreadEndTime - mThisEventStartThreadTime;
if (eventCostTime >= mLongBlockThresholdMillis) {
mBlockListener.onBlockEvent(eventCostTime, eventCostThreadTime, true, mThisEventStartTime,
thisEventEndTime, mLongBlockThresholdMillis, mShortBlockThresholdMillis);
} else if (eventCostTime >= mShortBlockThresholdMillis) {
mBlockListener.onBlockEvent(eventCostTime, eventCostThreadTime, false, mThisEventStartTime,
thisEventEndTime, mLongBlockThresholdMillis, mShortBlockThresholdMillis);
}
mBlockListener.onEventEnd(thisEventEndTime);
}
}
}
由于源码中,println()
会依次打印两次,在主线程函数执行开始调用一次,在主线程函数执行结束调用一次,利用这个规律
- 通过
mEventStart
变量来控制当前是事件开始还是结束 - 事件结束后,通过时间差计算出当前函数的时间耗时和当前线程的时间耗时
- 如果超过规定的
mLongBlockThresholdMillis
时间,则触发长时间的阻塞行为 - 如果超过规定的
mShortBlockThresholdMillis
时间,则触发短时间的阻塞行为
@Override
public void println(String x) {
if (!mEventStart) {// 事件开始
mThisEventStartTime = System.currentTimeMillis();
mThisEventStartThreadTime = SystemClock.currentThreadTimeMillis();
mEventStart = true;
mBlockListener.onEventStart(mThisEventStartTime);
} else {// 事件结束
final long thisEventEndTime = System.currentTimeMillis();
final long thisEventThreadEndTime = SystemClock.currentThreadTimeMillis();
mEventStart = false;
long eventCostTime = thisEventEndTime - mThisEventStartTime;
long eventCostThreadTime = thisEventThreadEndTime - mThisEventStartThreadTime;
if (eventCostTime >= mLongBlockThresholdMillis) {
mBlockListener.onBlockEvent(eventCostTime, eventCostThreadTime, true, mThisEventStartTime,
thisEventEndTime, mLongBlockThresholdMillis, mShortBlockThresholdMillis);
} else if (eventCostTime >= mShortBlockThresholdMillis) {
mBlockListener.onBlockEvent(eventCostTime, eventCostThreadTime, false, mThisEventStartTime,
thisEventEndTime, mLongBlockThresholdMillis, mShortBlockThresholdMillis);
}
mBlockListener.onEventEnd(thisEventEndTime);
}
}
在Sm采集过程中,涉及到堆栈的采集和CPU采集,它们的原理相似,通过定时采集当前的堆栈和CPU,以堆栈采集为例
- 在采集过程中都会保存在集合里面,且超过了最大容量时,则会移除最后一个
- 在采集过程中会记录每次采集的开始时间
System.currentTimeMillis()
public class StackSampler extends AbstractSampler {
......
@Override
protected void doSample() {
synchronized (sStackMap) {
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
sStackMap.put(System.currentTimeMillis(), mCurrentThread.getStackTrace());
}
}
}
这里需要注意的是堆栈的采集时间需要比发生阻塞的设置时长要短,如果主线程发生阻塞500ms的时候,那么采集堆栈如果设置为200ms的时候,就会在阻塞过程中产生2次采集,采集到的就是当前阻塞的堆栈,在获取堆栈的时候也会判断,当前采集堆栈的时间,是否在发生阻塞的时间区间内
Map<Long, List<StackTraceElement>> getThreadStackEntries(long startTime, long endTime) {
Map<Long, List<StackTraceElement>> result = new LinkedHashMap<>();
synchronized (sStackMap) {
for (Long entryTime : sStackMap.keySet()) {
//是否在发生阻塞的时间区间内
if (startTime < entryTime && entryTime < endTime) {
result.put(entryTime, Arrays.asList(sStackMap.get(entryTime)));
}
}
}
return result;
}
3、总结
Sm的采集原理用的是BlockCanary一样的原理,在此之外增加堆栈采集和CPU的采集,通过阻塞的时间区间作为标准获取对应的堆栈和CPU
本文地址:https://blog.csdn.net/qq_30379689/article/details/111881903