android优化-渲染和布局优化

1,143 阅读5分钟

先说一下 setContentView 的大致流程

当我们调用 setContentView(int resId) 的时候,我们会先在 window 中初始化一个 DecorView,Decorview 是继承 FrameLayout,本身是把 Decorview 添加到window上的。当我们调用 setContentView(int resId) 时,会把 这个布局id 对应的文件 利用 LayoutInflater.from(mContext).inflate(resId, contentParent) ,添加到这个 DecorView 中。然而 LayoutInflater 创建View的时候是利用 XmlResourceParser 读取文件,然后递归遍历, 反射出来对应的View,并设置到 DecorView 中。

再说一说 渲染机制

大多数手机的屏幕刷新频率是60hz(现在有的手机已经是90hz(华为p40,vivo x50) 或者是 120hz了(vivo x50 pro+,一加 8T))。CPU与GPU。CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。例如显示图片的时候,需要先经过CPU的计算加载到内存中,然后传递给GPU进行渲染。文字的显示比较复杂,需要先经过CPU换算成纹理,然后交给GPU进行渲染,返回到CPU绘制单个字符的时候,再重新引用经过GPU渲染的内容。动画则存在一个更加复杂的操作流程。

我们采取的措施

采用 merge

merge 可以减少一层层级,当我们自定义组合View的时候,一般是直接 View.inflate(context, R.layout.layout_tool_view, this); 比如当我们的自定的view是 LinearLayout,然而,inflate的根布局也是 LinearLayout,此时我们可以把 xml中的 根布局换成 merge,来减少一层

采用 include

当我们复用 一个 layout的时候,我们可以使用 include ,配合 merge 来减少一层

使用 ViewStub

ViewStub 其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置,俗称懒加载View,相当于一个占位符,可以理解成一个非常轻量级的View

合理使用RelativeLayout和LinearLayout

当一个布局可以使用 RelativeLayout和 LinearLayout 都能完成任务的时候,我们要使用LinearLayout,因为RelativeLayout要测量子View 两次,LinearLayout 只需要一次即可(假如设置了measureWithLargestChild,就会使子View测量两次,严格来说有可能是两次)

ConstraintLayout

估计再复杂的布局,也会使用ConstraintLayout来完成。性能大大提高。开销要低得多。该类使用自己的约束解析系统,采用与标准布局完全不同的方式来解析视图之间的关系。

尽量规定View的宽高,或者是 match_parent

因为 wrap_content 会增大测量的开销

移除不必要的背景

默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容。但是,当布局具有背景时,其有可能会导致过度绘制。当子View的视图完全覆盖父视图的背景时,完全可以移除父亲的背景。当我们设置了window

减少使用透明度

在屏幕上渲染透明像素,即所谓的透明度渲染,是导致过度绘制的重要因素。在普通的过度绘制中,系统会在已绘制的现有像素上绘制不透明的像素,从而将其完全遮盖,与此不同的是,透明对象需要先绘制现有的像素,以便达到正确的混合效果。诸如透明动画、淡出和阴影之类的视觉效果都会涉及某种透明度,因此有可能导致严重的过度绘制。您可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制。例如,如需获得灰色文本,您可以在 TextView 中绘制黑色文本,再为其设置半透明的透明度值。但是,您可以简单地通过用灰色绘制文本来获得同样的效果,而且能够大幅提升性能。

自定义View 可以使用ClipRect

通过canvas.clipRect()来裁剪矩形,避免重复绘制

invalidate(Rect dirty) 已失效,无用

其他的一下外部一些优化

AsynclayoutInflater 异步加载View

把耗时的布局渲染操作放在子线程中,等inflate操作完成后再回调到主线程 有局限

  • 使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的;
  • 所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare );
  • 异步转换出来的 View 并没有被加到 parent view中,AsyncLayoutInflater 是调用了 LayoutInflater.inflate(int, ViewGroup, false),因此如果需要加到 parent view 中,就需要我们自己手动添加;
  • AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;
  • 不支持加载包含 Fragment 的 layout;
  • 如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局;

X2C

采用APT技术 来完成编译期间【注解】->【解注解】->【翻译xml】->【生成java】整个流程的操作,相当于直接new 的View,相当于在编译期间把Xml转成View的,也有自己的局限性。不建议用

Jetpack Compose

Google推出来的Compose 旨在舍弃xml,使用声明性的函数构建一个简单的界面组件。您无需修改任何 XML 布局,也不需要直接创建界面微件,而只需要调用 Jetpack Compose 函数来声明您想要的元素,Compose 编译器即会完成后面的所有工作

使用 Layout Inspector 查看层级

GPU 渲染速度和过度绘制

Systrace

Android Studio Profiler