Skip to content

Latest commit

 

History

History
247 lines (183 loc) · 9.81 KB

performance_layout2.md

File metadata and controls

247 lines (183 loc) · 9.81 KB

[TOC]

二、布局优化

2.1 Android绘制原理

2.1.1 CPUGPU结构

渲染操作通常依赖于两个核心组件:CPUGPU,其结构如下图所示:

image

  1. 蓝色的Control为控制器,用于协调控制整个CPU的运行,包括取出指令、控制其他模块的运行等;
  2. 绿色的ALU(Arithmetic Logic Unit)为算术逻辑单元,用于数学以及逻辑运算;
  3. 橙色的CacheDRAM分别为缓存和RAM,用户存储信息;

CPU控制器比较复杂,ALU数量较少,因此CPU擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。

2.1.2 CPUGPU功能

CPU负责包括Measure,Layout,Record,Execute等操作,GPU负责Rasterization(栅格化)操作。

image

2.1.3 XML布局显示到屏幕的流程

image

2.1.4 为什么是60fps

  • 人眼与大脑之间的协作无法感知超过60fps的画面更新;
  • 12fps:手动快速翻动书籍的帧率;
  • 24fps:电影使用的频率;
  • 30fps:实时音视频的帧率;
  • 60fps:手机交互过程中,需要触摸和反馈,需要60帧才能到达不卡顿的效果。
public class FpsUtil {
    private static long startTime;
    private static int count = 0;
    private static final long INTERVAL = 160 * 1000 * 1000; //160ms

    private static Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            if(startTime == 0) {
                startTime = frameTimeNanos; //纳秒
            }
            long interval = frameTimeNanos - startTime;
            if(interval > INTERVAL) {
                double fps = (((double) (count * 1000 * 1000)) / interval) * 1000L; // fps/1000L = (count * 1000 * 1000) / INTERVAL
                LogUtil.d("FPS", "fps=" + fps);
                startTime = 0;
                count = 0;
            }else {
                count++;
            }
            Choreographer.getInstance().postFrameCallback(this);
        }
    };

    public static void getFps() {
        Choreographer.getInstance().postFrameCallback(frameCallback);
    }

    public static void stopGetFps() {
        Choreographer.getInstance().removeFrameCallback(frameCallback);
    }
}

2.1.5 VSYNC

  • Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染;
  • Refresh Rate:代表了屏幕在1S内刷新屏幕的次数,这取决于硬件的固定参数,如60HZ
  • Frame Rate:代表了GPU1S内绘制操作的帧数,例如30fps,60fps

帧率 > 刷新率:画面撕裂(上半部分渲染的是前一帧,下半部分渲染的是后一帧)

刷新率 > 帧率:两个刷新周期渲染了同一帧,丢帧、画面卡顿 <-- 大部分情况

2.2 布局加载原理

image

@Override
protected void onCreate(Bundle savedInstanceState) {
  installCustomFactory();
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
}

private void installCustomFactory() {
  LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
    @Nullable
    @Override
    public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
      //handle
      LogUtil.e("onCreateView", "name=" + name);
      return getDelegate().createView(parent, name, context, attrs);
      // E/onCreateView: 2/name=LinearLayout
      // E/onCreateView: 2/name=ViewStub
      // E/onCreateView: 2/name=FrameLayout
      // E/onCreateView: 2/name=androidx.appcompat.widget.ActionBarOverlayLayout
      // E/onCreateView: 2/name=androidx.appcompat.widget.ContentFrameLayout
      // E/onCreateView: 2/name=androidx.appcompat.widget.ActionBarContainer
      // E/onCreateView: 2/name=androidx.appcompat.widget.Toolbar
      // E/onCreateView: 2/name=androidx.appcompat.widget.ActionBarContextView
      // E/onCreateView: 2/name=androidx.constraintlayout.widget.ConstraintLayout
      // E/onCreateView: 2/name=TextView
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
      return null;
    }
  });
}

2.3 布局优化方式

2.3.1 过渡绘制

Overdraw(过渡绘制)描述的是屏幕上某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次,这就浪费大量的CPU以及GPU资源。

优化方式:

  • 移除Window默认的background
  • 移除XML布局文件中的非必须的background
  • 按需显示占位图片;
if(chat.getAuthor().getAvatarId() == 0) {
  Picasso.with(getContext()).load(android.R.color.transparent).into(char_author_avatar);
  chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());
}else {
  Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(chat_author_avatar);
  chat_author_avatar.setBackgroundColor(Color.TRANSPARENT);
}
  • 对于复杂的自定义viewAndroid系统无法监控并自动优化;
  • 采用canvas.clipRect来帮助系统识别那些可见区域;
  • 采用canvas.quickreject来判断是否相交。
private void drawDroidCard(Canvas canvas, List<DroidCard> mDroidCards, int i) {
  DroidCard card = mDroidCards.get(i);
  canvas.save(); //画布保存
  canvas.clipRect(card.x, 0f, (mDroidCards.get(i + 1).x), card.height); //关键计算画布大小
  canvas.drawBitmap(card.bitmap, card.x, 0f, paint);
  canvas.restore(); //画布裁剪
}

2.3.2 标签优化

  • include标签的使用:抽取公用xml,使用时包含进来;

  • merge标签的使用merge标签主要用于辅助include标签,在使用include后可能导致布局嵌套过多,多余的layout节点或导致解析变慢(可通过hierarchy viewer工具查看布局的嵌套情况);官方文档说明:merge用于消除视图层次结构中的冗余视图;

    merge标签常用场景: 根布局是FrameLayout且不需要设置backgroundpadding等属性,可以用merge代替,因为ActivityContentView父元素就是FrameLayout,所以可以用merge消除只剩一个. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中. 自定义View如果继承LinearLayout(ViewGroup),建议让自定义View的布局文件根布局设置成merge,这样能少一层结点.

  • ViewStub标签的使用:最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用ViewStub标签,以减少内存使用量,加快渲染速度.ViewStub是一个不可见的,实际上是把宽高设置为0的View.效果有点类似普通的view.setVisible(),但性能体验提高不少。

2.3.3 布局的选择

  • 减少嵌套层级
  • 优先使用线性布局
  • ConstraintLayout终极大招

2.4 布局加载优化方式

2.4.1 采用Java代码加载

  • xml由于有IO操作与反射操作导致加载耗时;
  • 采用Java方式构造布局,代码量大,不容易实现,不容易维护;

解决方法github第三方库X2C,其地址为 https://github.com/TomasYu/X2C ,其核心思路是吧xml翻译成Java文件,减少系统里有LayoutInflate去解析xml的过程。

使用方式

annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'
  
@Xml(layout="activity_main")
  
this.setContentView(R.layout.activity_main);
-->X2C.setContentView(this, R.layout.activity_main);

LayoutInflater.from(this).inflate(R.layout.activity_main,null); 
--> X2C.inflate(this,R.layout.activity_main,null);

原理分析

①什么时候解析xml

annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'

这里指定了annotationProcessor,也就是注解编译处理器,即APT技术;Javac编译的时候回调用这个处理器,传入注解,解析xmlX2C通过scanLayouts方法扫码项目的res/layout中的一系列文件来找到xml文件)。

②找到xml文件后如何解析?

com.zhangyue.we.view.View#translate(...)方法中通过拼接字符串的方式解析,然后统计javapoet技术生成Java代码文件。

参考:源码解析:解析掌阅X2C 框架

2.4.2 异步加载

使用Androidx的库asynclayoutinflater:

**注意:**不能有依赖于主线程的操作。

implementation 'androidx.asynclayoutinflater:asynclayoutinflater:1.0.0'
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        installCustomFactory();
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        //异步加载
        new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null,
                new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
              	setContentView(view);
                afterSetView();
            }
        });
        //afterSetView();
    }