APM框架Matrix源码分析(四)FrameTracer帧率监控

256 阅读8分钟

直接从FrameTracer的入口看起

@Override
public void onAlive() {
    super.onAlive();
    if (config.isFPSEnable()) {
      forceEnable();
    }
}

public void forceEnable() {
    MatrixLog.i(TAG, "forceEnable");
    //Android 7.0及以上
    if (sdkInt >= Build.VERSION_CODES.N) {
      //注册Activity生命周期回调
      Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
      sceneFrameCollector = new SceneFrameCollector();
      //添加监听,对应observer.onFrameMetricsAvailable
      addListener(sceneFrameCollector);
      //设置SceneFrameCollectItem的listener,listener会回调上报
      register(new AllSceneFrameListener());
    } else {
      //接收到信号的时候回调这些监听就可以让 FrameTracer 接收到每帧的回调
      UIThreadMonitor.getMonitor().addObserver(looperObserver);
    }
}
Android 7.0及以上

先注册了Activity生命周期回调

//线程安全ConcurrentHashMap,用来存放activity的window对应的渲染监听器
private final Map<Integer, Window.OnFrameMetricsAvailableListener> 
  	frameListenerMap = new ConcurrentHashMap<>();
//在Activity可见时添加监听
@RequiresApi(Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
    //避免重复添加
    if (frameListenerMap.containsKey(activity.hashCode())) {
      return;
    }
    //获取刷新频率
    defaultRefreshRate = getRefreshRate(activity.getWindow());
    MatrixLog.i(TAG, "default refresh rate is %dHz", (int) defaultRefreshRate);
    //Google官方推荐使用 OnFrameMetricsAvailableListener 测量布局渲染时间(用于Android7.0及以上)
    Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() {

      //这里应该是 Build.VERSION_CODES.N
      @RequiresApi(api = Build.VERSION_CODES.O)
      @Override
      public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
        //只有在前台才会去做数据处理,应用不可见时不会进行UI渲染
        if (isForeground()) {
          //跳过无意义数据的回调
          // skip not available metrics.
          for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
            long v = frameMetrics.getMetric(FrameDuration.indices[i]);
            if (v < 0 || v >= HALF_MAX) {
              // some devices will produce outliers, especially the Honor series, eg: NTH-AN00, ANY-AN00, etc.
              return;
            }
          }
          //先copy一份, 之后从copy的数据中获取对应渲染时间参数
          FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
          //TOTAL_DURATION: CPU 渲染到传递到 GPU 所用的总时间, 上述所花费的有意义的时间之和 , 单位纳秒
          long totalDuration = frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION);
          //不掉帧时的数据
          float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / cachedRefreshRate;
          //计算掉帧数
          float droppedFrames = Math.max(0f, (totalDuration - frameIntervalNanos) / frameIntervalNanos);
          //累加
          droppedSum += droppedFrames;
          //【掉帧收集】掉帧数>=阈值,收集起来(不指定dropFrameListener不会收集)
          if (dropFrameListener != null && droppedFrames >= cachedThreshold) {
            dropFrameListener.onFrameMetricsAvailable(
              ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
              frameMetricsCopy, droppedFrames, cachedRefreshRate);
          }
          synchronized (listeners) {
            //【收集所有】回调的SceneFrameCollector的onFrameMetricsAvailable,对应上面addListener(sceneFrameCollector)
            for (IFrameListener observer : listeners) {
              observer.onFrameMetricsAvailable(
                ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
                frameMetricsCopy, droppedFrames, cachedRefreshRate);
            }
          }
        }
      }
    };
    //监听器存入map,每个Activity对应一个监听器
    this.frameListenerMap.put(activity.hashCode(), onFrameMetricsAvailableListener);
    //向Activity界面的Window窗口对象,添加监听器,同时设置Handler,测量渲染在这个Handler所在的线程执行
    activity.getWindow().addOnFrameMetricsAvailableListener(
      onFrameMetricsAvailableListener, MatrixHandlerThread.getDefaultHandler());
    MatrixLog.i(TAG, "onActivityResumed addOnFrameMetricsAvailableListener");
}

@Override
public void onActivityDestroyed(Activity activity) {
    try {
     //onDestory时移除监听
      activity.getWindow().removeOnFrameMetricsAvailableListener(
        frameListenerMap.remove(activity.hashCode()));
    } catch (Throwable t) {
      MatrixLog.e(TAG, "removeOnFrameMetricsAvailableListener error : " + t.getMessage());
    }
}

在Activity可见时调用activity.getWindow().addOnFrameMetricsAvailableListener向Activity界面的Window窗口对象,添加监听器,onFrameMetricsAvailable回调中FrameMetrics 封装了渲染时间(包含总耗时/绘制耗时/布局耗时/动画耗时/测量耗等),计算掉帧数据并收集。

接着看有意义数据的收集observer.onFrameMetricsAvailable

@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollector implements IFrameListener {
    //上述register(new AllSceneFrameListener());会调到这里
    public synchronized void register(@NonNull ISceneFrameListener listener) {
      //省略listener参数不合法判断。。。
      //如果设置了场景(哪个页面),按照场景统计
      String scene = listener.getName();
      //创建SceneFrameCollectItem并把listener传入,存放到两个map中
      SceneFrameCollectItem collectItem = new SceneFrameCollectItem(listener);
      if (scene == null || scene.isEmpty()) {
        //如果没设置场景,按照AllSceneFrameListener和FrameDecorator(帧率UI展示,如果设置了)
        unspecifiedSceneMap.put(listener, collectItem);
      } else {
        //设置了场景,按照场景统计
        specifiedSceneMap.put(scene, collectItem);
      }
    }


    //有意义数据的收集`observer.onFrameMetricsAvailable`回调
    @Override
    public void onFrameMetricsAvailable(
      final String sceneName, final FrameMetrics frameMetrics,
      final float droppedFrames, final float refreshRate) {

      frameHandler.post(new Runnable() {
        @Override
        public void run() {
          String scene = sceneName.getClass().getName();
          synchronized (SceneFrameCollector.this) {
            SceneFrameCollectItem collectItem = specifiedSceneMap.get(scene);
            //从specifiedSceneMap中取数据
            if (collectItem != null) {
              collectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate);
            }
            //处理未设置场景的数据
            for (SceneFrameCollectItem frameCollectItem : unspecifiedSceneMap.values()) {
              frameCollectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate);
            }
          }
        }
      });
    }
}

append会调到SceneFrameCollectItem的append方法

@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollectItem {
    //【维度1】每个区间的 掉帧次数,用来统计占比
    private final int[] dropLevel = new int[DropStatus.values().length];
    //【维度2】每个区间的 掉帧数之和,用来统计分布
    private final int[] dropSum = new int[DropStatus.values().length];
    //上述new SceneFrameCollectItem(listener)会把listener传入
    SceneFrameCollectItem(ISceneFrameListener listener) {
      this.listener = listener;
    }

    public void append(String scene, FrameMetrics frameMetrics, float droppedFrames, float refreshRate) {
      //判断不统计的情况
      //(设置跳过第一帧 && 当前是第一帧  FIRST_DRAW_FRAME:绘制的该帧是否是第一帧, 0 不是, 1 是 )|| 掉帧数 < 阈值(默认0,就是都统计)
      if ((listener.skipFirstFrame() && frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1)
          || droppedFrames < (refreshRate / 60) * listener.getThreshold()) {
        return;
      }
      //记录beginMs 
      if (count == 0) {
        beginMs = SystemClock.uptimeMillis();
      }
      //记录所有的花费的时间
      for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
        durations[i] += frameMetrics.getMetric(FrameDuration.indices[i]);
      }
      //Android12及以上记录GPU_DURATION:表示此帧在GPU上完成所用的总时间(以纳秒为单位)
      if (sdkInt >= Build.VERSION_CODES.S) {
        durations[FrameDuration.GPU_DURATION.ordinal()] += frameMetrics.getMetric(FrameMetrics.GPU_DURATION);
      }
      //掉帧数累加
      dropCount += droppedFrames;
      //统计流畅度的占比情况
      collect(Math.round(droppedFrames));
      this.refreshRate += refreshRate;
      //不掉帧时的时间
      float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / refreshRate;
      //记录实际花费的总时间
      totalDuration += Math.max(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION), frameIntervalNanos);
      //计数+1
      ++count;

      lastScene = scene;
      //10秒上报一次
      if (SystemClock.uptimeMillis() - beginMs >= listener.getIntervalMs()) {
        //计算+回调上报+重置参数
        tryCallBackAndReset();
      }
    }
}

上述代码主要是将回调的FrameMetrics参数通过计算和统计并上报的过程。

Matrix定义了一套衡量流畅度的标准:DROPPED_BEST, DROPPED_NORMAL, DROPPED_MIDDLE, DROPPED_HIGH, DROPPED_FROZEN;一共5个区间。

统计按照两个维度:

  • dropLevel 每个区间的 掉帧次数,用来统计占比
  • dropSum 每个区间的 掉帧数之和,用来统计分布
Android7.0以下

UIThreadMonitor 拥有监听系统 VSync 信号的能力,在接收到信号的时候回调这些监听就可以让 FrameTracer 接收到每帧的doFrame回调。(这里暂时先不管UIThreadMonitor怎么实现的此功能,后面分析)

private LooperObserver looperObserver = new LooperObserver() {
    /**
     * 每一帧的回调
     *
     * @param focusedActivity 当前顶层Activity的名字
     * @param startNs 消息开始分发的时间点,单位纳秒
     * @param endNs 消息分发结束的时间点,单位纳秒
     * @param isVsyncFrame 是否是UI渲染帧
     * @param intendedFrameTimeNs 收到信号的时间点,单位纳秒。反射Choreographer内部类FrameDisplayEventReceiver中的mTimestampNanos
     * @param inputCostNs input耗时
     * @param animationCostNs animation耗时
     * @param traversalCostNs traversal耗时
     * 
     */		
  @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);
    }
  }

@Deprecated
private void notifyListener(final String focusedActivity, final long startNs, final long endNs, final boolean isVsyncFrame,
                              final long intendedFrameTimeNs, final long inputCostNs, final long animationCostNs, final long traversalCostNs) {
    long traceBegin = System.currentTimeMillis();
    try {
      //实际耗时的纳秒
      final long jitter = endNs - intendedFrameTimeNs;
      //掉帧数 = 实际耗时的纳秒/每帧的纳秒
      final int dropFrame = (int) (jitter / frameIntervalNs);
      //可以设置oldDropFrameListener及阈值来监听掉帧
      if (oldDropFrameListener != null && dropFrame > dropFrameListenerThreshold) {
        try {
          if (MatrixUtil.getTopActivityName() != null) {
            oldDropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName());
          }
        } catch (Exception e) {
          MatrixLog.e(TAG, "dropFrameListener error e:" + e.getMessage());
        }
      }
      //计算总的掉帧数和时间
      droppedSum += dropFrame;
      durationSum += Math.max(jitter, frameIntervalNs);

      //可以添加listener到oldListeners来监听
      synchronized (oldListeners) {
        for (final IDoFrameListener listener : oldListeners) {
          //这里只分析listener.collect,省略其它回调
          //这里的listener可以传入FPSCollector(最新版没有),默认300帧一分析
          if (null != listener.getExecutor()) {
            if (listener.getIntervalFrameReplay() > 0) {
              //收集上报
              listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame,
                  intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
            } 
          } 
        }
      }
    } finally {
      //debug下日志打印掉帧
      long cost = System.currentTimeMillis() - traceBegin;
      if (config.isDebug() && cost > frameIntervalNs) {
        MatrixLog.w(TAG, "[notifyListener] warm! maybe do heavy work in doFrameSync! size:%s cost:%sms", oldListeners.size(), cost);
      }
    }
  }
};

listener.collect会走到IDoFrameListener的collect

@CallSuper
public void collect(String focusedActivity, long startNs, long endNs, int dropFrame, 
                    boolean isVsyncFrame,long intendedFrameTimeNs, long inputCostNs, 
                    long animationCostNs, long traversalCostNs) {
  	//从链表缓冲池中取FrameReplay(存每一帧的信息),没有则new一个
    FrameReplay replay = FrameReplay.create();
  	//下面都是属性赋值
    replay.focusedActivity = focusedActivity;
    replay.startNs = startNs;
    replay.endNs = endNs;
    replay.dropFrame = dropFrame;
    replay.isVsyncFrame = isVsyncFrame;
    replay.intendedFrameTimeNs = intendedFrameTimeNs;
    replay.inputCostNs = inputCostNs;
    replay.animationCostNs = animationCostNs;
    replay.traversalCostNs = traversalCostNs;
    list.add(replay);
  	//到达阈值 & 有提供线程执行器 开始分析
    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();
                }
            }
        });
    }
}

//FrameReplay链表缓存池,用于复用
private final static LinkedList<FrameReplay> sPool = new LinkedList<>();

public static final class FrameReplay {
    public String focusedActivity;
    public long startNs;
    public long endNs;
    public int dropFrame;
    public boolean isVsyncFrame;
    public long intendedFrameTimeNs;
    public long inputCostNs;
    public long animationCostNs;
    public long traversalCostNs;
    //回收,将对象放入缓存池,只擦除属性,方便下次复用
    public void recycle() {
      //最大缓存1000条
      if (sPool.size() <= 1000) {
        this.focusedActivity = "";
        this.startNs = 0;
        this.endNs = 0;
        this.dropFrame = 0;
        this.isVsyncFrame = false;
        this.intendedFrameTimeNs = 0;
        this.inputCostNs = 0;
        this.animationCostNs = 0;
        this.traversalCostNs = 0;
        synchronized (sPool) {
          sPool.add(this);
        }
      }
    }
    //复用
    public static FrameReplay create() {
      FrameReplay replay;
      synchronized (sPool) {
        //先从缓存池中取
        replay = sPool.poll();
      }
      if (replay == null) {
        //取不到则new一个
        return new FrameReplay();
      }
      return replay;
    }
}

对于频繁创建对象的处理是使用一个链表缓冲池

  • 使用时先从缓存池中取对象,没有则创建一个
  • 回收时,限制缓存最大数量,只擦除属性,并添加到缓存池

接着看下v2.0.8的FrameTracer的内部类FPSCollector

gitee.com/bestchangge…

FPSCollector复写IDoFrameListener的doReplay方法

private class FPSCollector extends IDoFrameListener {

	private Handler frameHandler = new 			Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
	//提供分析数据的线程池
  Executor executor = new Executor() {
    @Override
    public void execute(Runnable command) {
      frameHandler.post(command);
    }
  };
	//用来聚合页面的帧信息
  private HashMap<String, FrameCollectItem> map = new HashMap<>();
  //提供线程池
  @Override
  public Executor getExecutor() {
    return executor;
  }
	//多少帧分析一次
  @Override
  public int getIntervalFrameReplay() {
    return 300;
  }

  @Override
  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);
    }
  }

  public void doReplayInner(String visibleScene, long startNs, long endNs, 
                            int droppedFrames,boolean isVsyncFrame, 
                            long intendedFrameTimeNs, long inputCostNs,
                            long animationCostNs, long traversalCostNs) {
      //当前顶层Activity为空
      if (Utils.isEmpty(visibleScene)) return;
      //isVsyncFrame表示是否是UI渲染帧,只关注帧率
      if (!isVsyncFrame) return;
      //从页面的维度聚合,将页面和每一帧关联
      FrameCollectItem item = map.get(visibleScene);
      if (null == item) {
        item = new FrameCollectItem(visibleScene);
        map.put(visibleScene, item);
      }
      //对每个页面的帧信息根据掉帧数划分等级
      item.collect(droppedFrames);
      //10秒上报一次
      if (item.sumFrameCost >= timeSliceMs) { // report
        map.remove(visibleScene);
        //上报
        item.report();
      }
  }
}

小结:

FrameTrace帧率监控:

  1. 监听帧率信息回调,Android7.0及以上用系统API addOnFrameMetricsAvailableListener的方式,Android7.0以下通过自己实现的UIThreadMonitor(UI 线程的监视器,LooperPrinter和Choreographer实现)
  2. 收集每一帧的信息,当前页面名字、input、animation、traversal阶段的耗时,Android7.0及以上用系统API,统计的更精细。
  3. 定义了衡量流畅度的5个标椎,从页面的维度聚合数据,计算每个区间的掉帧次数,用来统计占比,计算每个区间的掉帧数之和,用来统计分布