这是功能优化系列之matrix框架的第10篇文章,我将在功能优化专栏中对matrix apm框架做一个全面的代码剖析,功能优化是Android高级工程师必知必会的点,也是面试过程中的高频标题,对功能优化感兴趣的小伙伴能够去我主页检查所有关于matrix的分享。

前言

matrix卡顿监控,下面几篇归于根底,可先行查阅,不然今日的内容读起来可能有点困难。

  • Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之gradle插件- 字节码插桩代码剖析
  • Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之AppMethodBeat专项剖析
  • Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之LooperMonitor源码剖析
  • Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析

有了上边几篇根底,咱们剖析其他类型的tracer会势如破竹。言归正传,今日咱们要剖析的是matrix卡顿监控中的另一种tracer-FrameTracer-帧率监控。为什么要监控帧率呢?根本原因是为了保证帧率的安稳。一般来讲,Android设备大多都是60fps的帧率(当然也有90fps、120fps的),也便是画面每秒更新60次,假设使用的帧率能安稳的维持在60fps的话,对用户来讲体验是最好的。而要保证帧率维持在60fps,那么就要求每次改写在16.66毫秒内完结。咱们知道Android的改写是基于VSYNV信号的,Android体系每隔16.66毫秒宣布一次VSYNC信号,触发对UI进行渲染,VSYNC机制保证了Android的改写频率维持在一个固定的距离内,有利于帧率的安稳。所以FrameTracer存在的价值便是监控帧率是否安稳这一问题,也是卡顿监控中的一环。

FrameTracer是在TracePlugin中被初始化和启动,咱们从它的几个要害办法下手深入源码探索。

  • 结构办法
  • onStartTrace
  • onStopTrace

结构办法

传入的supportFrameMetrics是一个boolean变量,当体系版本大于等于Android O时(8.0)为true。

public FrameTracer(TraceConfig config, boolean supportFrameMetrics) {
    useFrameMetrics = supportFrameMetrics;
    this.config = config;
    //frameIntervalNs是从Choreographer上反射获取的值,一般是16.66毫秒(这儿是纳秒为单位)
    //,不过与机型的改写率有关,默许机型便是60Hz的改写率,所以这儿等于16666667L纳秒
    this.frameIntervalNs = UIThreadMonitor.getMonitor().getFrameIntervalNanos();
    this.timeSliceMs = config.getTimeSliceMs();
    this.isFPSEnable = config.isFPSEnable();
    //下边是对帧率下降程度定义的几种类型
    this.frozenThreshold = config.getFrozenThreshold();
    this.highThreshold = config.getHighThreshold();
    this.normalThreshold = config.getNormalThreshold();
    this.middleThreshold = config.getMiddleThreshold();
    if (isFPSEnable) {
        addListener(new FPSCollector());
    }
}

onStartTrace

onStartTrace会调用到onAlive, 能够看到isFPSEnable一定要为true,FrameTracer才会生效。

这儿供给了两种监控帧率的办法,一种针对8.0以下的机型,经过UIThreadMonitor的机制来完成,UIThreadMonitor的源码完成请查阅Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析;

另一种8.0及以上经过给Application注册监听来拿到Activity生命周期的回调,这样一来,使用内每一个Activity的创建或销毁就都在监控之内。这儿咱们只需要重视onActivityResumed和onActivityDestroyed办法。

@Override
public void onAlive() {
    super.onAlive();
    if (isFPSEnable) {
        //8.0以下的机型,经过UIThreadMonitor的机制来完成帧率的监控
        if (!useFrameMetrics) {
            UIThreadMonitor.getMonitor().addObserver(this);
        }
        Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
    }
}

咱们分别来看下这两种不同的完成方式,先从8.0以下的完成看起。

8.0以下UIThreadMonitor

UIThreadMonitor经过调用addObserver将当时类添加为监听者,在Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析中对UIThreadMonitor有专项剖析,这儿主要用到了它的回调办法doFrame,doFrame中会经过对Choreographer一些操作能够直接拿到使用改写相关的参数:

  • focusedActivity:当时页面activity
  • startNs:一帧改写的开始时刻
  • endNs:一帧改写结束的时刻
  • isVsyncFrame:是否是vsync
  • intendedFrameTimeNs:vsync回调的时刻
  • inputCostNs:input事情处理的时刻
  • animationCostNs:动画处理的时刻
  • traversalCostNs:traversal事情处理的时刻

UIThreadMonitor是怎么拿到这些数据的?请查阅Android功能优化系列-腾讯matrix-TracePlugin卡顿优化之UIThreadMonitor源码剖析。

在doFrame办法中,使用处于前台时,会去汇总这些参数,然后调用notifyListener,notifyListener内部逻辑见后续剖析,因为8.0以上也会用到这个办法。

@Override
public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
    if (isForeground()) {
        notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
    }
}

8.0以上registerActivityLifecycleCallbacks

8.0以上Android体系直接暴露了相关api,addOnFrameMetricsAvailableListener,能够直接调用这个办法注册监听,去获取到相关信息(其实7.0就现已有了这个api,仅仅为什么matrix8.0才使用?咱们暂时不重视了)。

onActivityResumed

当useFrameMetrics为true时,表明当时体系为8.0及以。这儿要害的代码是addOnFrameMetricsAvailableListener,经过调用window的addOnFrameMetricsAvailableListener办法注册一个监听,从而能够收到体系的回调。

@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
    if (useFrameMetrics) {
        //拿到改写率
        this.refreshRate = (int) activity.getWindowManager().getDefaultDisplay().getRefreshRate();
        //1s除以改写率得到的便是每帧履行的时长,如前边提到的16.66毫秒
        this.frameIntervalNs = Constants.TIME_SECOND_TO_NANO / (long) refreshRate;
        Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            @Override
            public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
                //实践的vsync到来时刻
                long vsynTime = frameMetricsCopy.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
                //预期的vsync到来时刻, 假设此值与 VSYNC_TIMESTAMP 不同,则表明 UI 线程上发生了堵塞,阻止了 UI 线程及时响应vsync信号
                long intendedVsyncTime = frameMetricsCopy.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP);
                frameMetricsCopy.getMetric(FrameMetrics.DRAW_DURATION);
                notifyListener(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), intendedVsyncTime, vsynTime, true, intendedVsyncTime, 0, 0, 0);
            }
        };
        activity.getWindow().addOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener, new Handler());
    }
}

这儿是取了VSYNC_TIMESTAMP和INTENDED_VSYNC_TIMESTAMP两个值,INTENDED_VSYNC_TIMESTAMP表明该帧的 vsync 信号预期应该宣布时刻,VSYNC_TIMESTAMP是该帧的vsync信号实践宣布时刻的时刻,二者的差值就能够看作中心卡顿发生的时刻。

其实经过这个接口还能够获取更多信息,咱们看下FrameMetrics中定义的几种信息类型:

//处理输入事情花费的时刻, 单位纳秒
public static final int INPUT_HANDLING_DURATION = 1;
//处理动画履行花费的时刻, 单位纳秒
public static final int ANIMATION_DURATION = 2;
//measure和layout总共花费的时刻
public static final int LAYOUT_MEASURE_DURATION = 3;
//draw制作花费的时刻
public static final int DRAW_DURATION = 4;
//DisplayLists与显现线程同步花费的时刻
public static final int SYNC_DURATION = 5;
//向 GPU 发送制作指令花费的时刻
public static final int COMMAND_ISSUE_DURATION = 6;
//帧缓冲区交换花费的时刻
public static final int SWAP_BUFFERS_DURATION = 7;
//所有操作总共花费的时刻
public static final int TOTAL_DURATION = 8;
//是否是第一帧
public static final int FIRST_DRAW_FRAME = 9;
//预期vsync信号宣布的时刻
public static final int INTENDED_VSYNC_TIMESTAMP = 10;
//实践的vsync信号宣布的时刻
public static final int VSYNC_TIMESTAMP = 11;
//GPU核算花费的时刻
public static final int GPU_DURATION = 12;

所以8.0以上的处理逻辑便是经过拿到FrameMetrics目标,FrameMetrics目标中贮存了各种时刻信息,这儿取出要害VSYNC_TIMESTAMP和INTENDED_VSYNC_TIMESTAMP两个要害信息之后,就进入了notifyListener办法,可见8.0及以上和8.0以下仅仅获取时刻长度的方式不同,终究都是进入了notifyListener办法,异曲同工。

onActivityDestroyed

移除FrameMetricsAvailableListener

public void onActivityDestroyed(Activity activity) {
    if (useFrameMetrics) {
        activity.getWindow().removeOnFrameMetricsAvailableListener(frameListenerMap.remove(activity.hashCode()));
    }
}

后续

经过上边两项的剖析之后咱们现已拿到了帧制作相关的时刻信息,拿到信息后进行了什么处理呢,这儿阅读一下后续的完成细节。

notifyListener

第一步,假设设置了掉帧监听接口,则根据两次时刻距离核算出掉帧的数量,当数量超越设定值时回调dropFrame。

//根据两次时刻距离核算出掉帧的数量
final long jitter = endNs - intendedFrameTimeNs;
final int dropFrame = (int) (jitter / frameIntervalNs);
if (dropFrameListener != null) {
    if (dropFrame > dropFrameListenerThreshold) {
        if (MatrixUtil.getTopActivityName() != null) {
            //当数量超越设定值时回调dropFrame
            dropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName(), lastResumeTime);
        }
    }
}

第二步,履行这儿

if (null != listener.getExecutor()) {
    //FrameTracer中默许值为300,所以一定是满意大于0的
    if (listener.getIntervalFrameReplay() > 0) {
        listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame,
                intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
    } 
}

collect

第三步,调用collect办法搜集信息。

@CallSuper
public void collect(String focusedActivity, long startNs, long endNs, int dropFrame, boolean isVsyncFrame,
                    long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
    FrameReplay replay = FrameReplay.create();
    //当时可见的Activity
    replay.focusedActivity = focusedActivity;
    replay.startNs = startNs;
    replay.endNs = endNs;
    //丢帧数
    replay.dropFrame = dropFrame;
    //是否是vsync
    replay.isVsyncFrame = isVsyncFrame;
    //vsync预期抵达时刻
    replay.intendedFrameTimeNs = intendedFrameTimeNs;
    //输入事情处理时刻,这儿为0
    replay.inputCostNs = inputCostNs;
    //动画事情处理时刻,这儿为0
    replay.animationCostNs = animationCostNs;
    //traversal事情处理时刻,这儿为0
    replay.traversalCostNs = traversalCostNs;
    list.add(replay);
    //intervalFrame默许为300,也便是默许搜集300条后doReplay一次
    if (list.size() >= intervalFrame && getExecutor() != null) {
        final List<FrameReplay> copy = new LinkedList<>(list);
        list.clear();
        getExecutor().execute(new Runnable() {
            @Override
            public void run() {
                doReplay(copy);
                for (FrameReplay record : copy) {
                    record.recycle();
                }
            }
        });
    }
}

doReplay

第四步,遍历这300条数据,对每个数据履行doReplayInner。

public void doReplay(List<FrameReplay> list) {
    super.doReplay(list);
    for (FrameReplay replay : list) {
        doReplayInner(replay.focusedActivity, replay.startNs, replay.endNs, replay.dropFrame, replay.isVsyncFrame,
                replay.intendedFrameTimeNs, replay.inputCostNs, replay.animationCostNs, replay.traversalCostNs);
    }
}

doReplayInner

第五步,visibleScene表明当时可见页面,以visibleScene为key,将visibleScene相同的数据封装到到一个FrameCollectItem目标中,并存入map, 调用collect。

public void doReplayInner(String visibleScene, long startNs, long endNs, int droppedFrames,
                          boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs,
                          long animationCostNs, long traversalCostNs) {
    FrameCollectItem item = map.get(visibleScene);
    if (null == item) {
        item = new FrameCollectItem(visibleScene);
        map.put(visibleScene, item);
    }
    item.collect(droppedFrames);
    if (item.sumFrameCost >= timeSliceMs) { // report
        map.remove(visibleScene);
        item.report();
    }
}

collect

第六步,collect办法中将丢帧的情况按照丢帧数量等级做了区分,分别为:

  • frozen等级-丢帧大于42
  • high等级-丢帧介于24到42
  • middle等级-丢帧介于9到24
  • mormal等级-丢帧介于3到9
  • best等级-丢帧小于3
void collect(int droppedFrames) {
    //frameIntervalNs表明每帧之间的时刻距离,如在60hz改写率的机型上为16.66毫秒,以纳秒
    //为单位表明则为16666666纳秒,TIME_MILLIS_TO_NANO是1毫秒用纳秒来表明的数字,则便是
    //1000000纳秒,所以终究核算得到frameIntervalCost为16.66
    float frameIntervalCost = 1f * FrameTracer.this.frameIntervalNs
            / Constants.TIME_MILLIS_TO_NANO;
    //根据丢掉的帧数乘以每帧的时长,得到的便是卡顿的总时长,以毫秒为单位
    sumFrameCost += (droppedFrames + 1) * frameIntervalCost;
    sumDroppedFrames += droppedFrames;
    sumFrame++;
    //假设丢帧数大于42,则以为丢帧度为“frozen”
    if (droppedFrames >= frozenThreshold) {
        dropLevel[DropStatus.DROPPED_FROZEN.index]++;
        dropSum[DropStatus.DROPPED_FROZEN.index] += droppedFrames;
    } 
    //假设丢帧数大于24可是小于42,则以为丢帧度为“high”
    else if (droppedFrames >= highThreshold) {
        dropLevel[DropStatus.DROPPED_HIGH.index]++;
        dropSum[DropStatus.DROPPED_HIGH.index] += droppedFrames;
    } 
    //假设丢帧数大于9可是小于24,则以为丢帧度为“middle”
    else if (droppedFrames >= middleThreshold) {
        dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
        dropSum[DropStatus.DROPPED_MIDDLE.index] += droppedFrames;
    } 
    //假设丢帧数大于3可是小于9,则以为丢帧度为“normal”
    else if (droppedFrames >= normalThreshold) {
        dropLevel[DropStatus.DROPPED_NORMAL.index]++;
        dropSum[DropStatus.DROPPED_NORMAL.index] += droppedFrames;
    } else {
    //不然以为帧率为最佳状况
        dropLevel[DropStatus.DROPPED_BEST.index]++;
        dropSum[DropStatus.DROPPED_BEST.index] += Math.max(droppedFrames, 0);
    }
}

第七步,当丢帧导致的卡顿时长超越timeSliceMs(默许10s)时,陈述卡顿问题。

if (item.sumFrameCost >= timeSliceMs) {
    map.remove(visibleScene);
    item.report();
}

report

简单看下陈述的参数

void report() {
    float fps = Math.min(refreshRate, 1000.f * sumFrame / sumFrameCost);
    resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication());
    //丢帧场景
    resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene);
    //丢帧程度
    resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
    //丢帧数
    resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
    //当时帧率
    resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
    Issue issue = new Issue();
    issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
    issue.setContent(resultObject);
    plugin.onDetectIssue(issue);
}

看一下打印的示例:

{
	"machine": "BAD",
	"cpu_app": 0,
	"mem": 1495580672,
	"mem_free": 654600,
	"scene": "sample.tencent.matrix.issue.IssuesListActivity",
	"dropLevel": {
		"DROPPED_FROZEN": 1,
		"DROPPED_HIGH": 0,
		"DROPPED_MIDDLE": 1,
		"DROPPED_NORMAL": 0,
		"DROPPED_BEST": 0
	},
	"dropSum": {
		"DROPPED_FROZEN": 2738,
		"DROPPED_HIGH": 0,
		"DROPPED_MIDDLE": 10,
		"DROPPED_NORMAL": 0,
		"DROPPED_BEST": 0
	},
	"fps": 0.04363667964935303
}

至此,帧率信息监听的代码就剖析完了。

onStopTrace

主要是资源清理操作。

public void onDead() {
    super.onDead();
    removeDropFrameListener();
    if (isFPSEnable) {
        UIThreadMonitor.getMonitor().removeObserver(this);
        Matrix.with().getApplication().unregisterActivityLifecycleCallbacks(this);
    }
}

总结

经过今日的剖析,咱们了解到FrameTracer进行帧率监听分为两种方式。以Android 8.0为分界线,8.0以下经过UIThreadMonitor完成,经过对主线程音讯行列中音讯的履行前后进行监听,核算出一帧画面履行过程中不同类型音讯的履行的时刻,然后进行核算剖析,达到监听帧率的作用;8.0及以上是经过Android体系供给的addOnFrameMetricsAvailableListener办法完成数据的获取,addOnFrameMetricsAvailableListener注册监听之后,能够拿到丰富的时刻信息为我所用,从而使监听帧率的完成更加的方便快捷,能够想象一下,假设未来某天,Android开发适配的最低机型为7.0时,那么使用体系api就足够帮咱们完成功能监控了,这是多么夸姣的一个期待。