Android UI 流畅度相关

来源:互联网 发布:工程动画软件 编辑:程序博客网 时间:2024/04/30 00:51

## Android 渲染机制
Android系统每隔16ms发出VSYNC((Vertical Synchronization 垂直同步))信号,触发对UI进行渲染。如果系统发出VSYNC信号,而此时无法进行渲染,还在做别的操作,那么就会导致丢帧的现象,即屏幕的刷新速率大于帧率。之所以是16ms,是因为人眼与大脑之间的协作无法感知超过60fps(1000ms/60=16.6ms)的画面更新,超过60fps也是无意义的。

## Overdraw
所谓的Overdraw(过度绘制)就是同一相素在一帧的时间内被多次绘制,特别是在多层次结构中,例如:多余的背景,看不见的View。在开发者选项中可以打开过度绘制的显示开关,之后就可以在我们的屏幕上看到蓝色,淡绿,淡红,深红4种颜色区域,代表了4种不同程度的Overdraw情况,具体如下:
a. True color: No overdraw (本色,无过度绘制)
b. Blue: Overdrawn once
c. Green: Overdrawn twice
d. Pink: Overdrawn three times
e. Red: Overdrawn four or more times

通过减小 ui叠加层级,去掉不必要的背景,在onDraw时使用Canvas.clipRect()来指定需要绘制的区域(例如:重叠的扑克牌只绘制能看到的,只有最后一张才绘制完整)等手段来减小Overdraw的区域;

## 帧率的分析
#### 一,GPU呈现模式分析
在开发者模式中打开GPU呈现模式分析(Profile GPU rendering),打开之后系统会记录每个页面最近128帧图像的绘制情况,然后执行 adb shell dumpsys gfxinfo packageName 命令得到相应数据,主要数据如下:

Profile data in ms: com.study.rubinchen.materialdesign/com.study.rubinchen.materialdesign.MainActivity/android.view.ViewRootImpl@93d51db (visibility=0)        Draw    Prepare Process Execute        2.68    0.76    6.41    1.79        2.39    0.71    6.79    2.08        3.84    0.91    6.61    1.83        2.86    0.73    6.79    1.91        ....    ....    ....    ....        5.49    0.43    10.22   2.31        2.09    0.83    6.57    1.90        2.29    0.76    6.58    2.08        2.10    0.60    6.33    2.25Stats since: 94042542733030nsTotal frames rendered: 1474Janky frames: 208 (14.11%)90th percentile: 18ms95th percentile: 21ms99th percentile: 28msNumber Missed Vsync: 3Number High input latency: 1Number Slow UI thread: 24Number Slow bitmap uploads: 2Number Slow issue draw commands: 127View hierarchy:com.study.rubinchen.materialdesign/com.study.rubinchen.materialdesign.MainActivity/android.view.ViewRootImpl@93d51db  48 views, 53.97 kB of display listsTotal ViewRootImpl: 1Total Views:        48Total DisplayList:  53.97 kB

重点在Profile data in ms这一个表格数据:
Draw:表示在Java中创建显示列表部分中,OnDraw()方法占用的时间。
Prepare:准备时间
Process:表示渲染引擎执行显示列表所花的时间,view越多,时间就越长 
Execute:表示把一帧数据发送到屏幕上排版显示实际花费的时间。其实是实际显示帧数据的后台缓存区与前台缓冲区交换后并将前台缓冲区的内容显示到屏幕上的时间。
将上面四个时间加起来就是绘制一帧需要的时间,如果超过16ms就有掉帧了。

二,Choreographer

Choreographer翻译过来为”舞蹈指挥”,这个类是Android 系统从4.1(API 16)加入来控制同步绘制、输入、动画三个 UI 操作的。并且他在每一frame被渲染时会有个callCack的回调,因此可以使用Choreographer#postFrameCallback设置自己的callback与Choreographer交互。需要注意的是每个线程都有自己的Choreographer。eg:

package com.study.rubinchen.recyclerview;import android.annotation.TargetApi;import android.os.Build;import android.util.Log;import android.view.Choreographer;@TargetApi(Build.VERSION_CODES.JELLY_BEAN)public class FrameSkipMonitor implements Choreographer.FrameCallback { public static final long ONE_FRAME_TIME = 16600000; // 1 Frame time cost   private static final long MIN_FRAME_TIME = 3 * ONE_FRAME_TIME; // 3 Frame time cost    private static final long MAX_FRAME_TIME = 20 * MIN_FRAME_TIME; // 60 Frame time cost, not record some special cases.    private static FrameSkipMonitor sInstance;    private long mRenderCount = 0;    private long mLastFrameNanoTime = 0;    private FrameSkipMonitor() {    }    public static FrameSkipMonitor getInstance() {        if (sInstance == null) {            sInstance = new FrameSkipMonitor();        }        return sInstance;    }    public void start() {        Choreographer.getInstance().postFrameCallback(FrameSkipMonitor.getInstance());    }    @Override    public void doFrame(long frameTimeNanos) {        mRenderCount++;        if (mLastFrameNanoTime > 0 && frameTimeNanos - mLastFrameNanoTime > MIN_FRAME_TIME) {            Log.e("TAG", "Junk-FrameInterval=" + (frameTimeNanos - mLastFrameNanoTime) + ",mRenderCount=" + mRenderCount);        }        mLastFrameNanoTime = frameTimeNanos;        Choreographer.getInstance().postFrameCallback(this);    }}

但上面的算法有个问题,到底一帧多少 ms 算掉帧,16ms不算,那么17ms呢?
有另外一种折中方案是:在一个 Activity 启动时(onResume())记录一个时间,在离开时(onPause())时记录一个时间,用这段时间内的帧数( mRenderCount)除以时间就能能得到一个帧率,虽然这个帧率不是100%准确,但对性能的分析,统计还是十分有帮助的,特别是对同一页面进行性能迭代比较时。

原创粉丝点击