- UI绘制优化好处,不仅可以减少卡顿,也可以加快启动速度,减少不必要的内存和cpu资源消耗。
- UI绘制主要工作就是减少CPU和GPU的工作量。
View绘制到屏幕流程
具体解说CPU与GPU绘制文章
简单描述:CPU加载xml转成对象后计算出向量图形,交给GPU光栅化,最后GPU交给硬件合成器决定最终显示在显示器的画面。
(1)首先应用主线里里的每个view都会经过老三部:measure,layout,draw.然后TextView,Button等等控件通过CPU计算转换为内存中的polygons(多边图形)和texture(纹理)。
(2)其次,CPU通过OpenGL的接口将纹理数据传递给GPU渲染处理,由于图形API不允许CPU直接与GPU通信,而是通过中间图形驱动层来连接两部分,驱动层维护了一个队列,CPU把display list添加到队列中,GPU从这个队列去除数据进行绘制,最终在屏幕上显示出来;在这个过程中,每个View都有一个DisplayList,由DisplayList这个结构负责保存绘制用到的所有信息,在Displaylist无需重新创建或改变的情况下,GPU可以直接使用这里的数据进行渲染.当View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。而不是像软件绘制那样需要向上递归。这样可以大大减少绘图的操作数量,因而提高了渲染效率.
(3)最后,GPU对图形数据进行渲染,通过匿名共享内存:SharedClient把需要显示的数据传到SurfaceFlinger;一个SharedClient对应一个Android应用程序,一个SharedClient可以创建31个SharedBufferStack;每个SharedBufferStack对应一个Surface,也就是一个Window;每个SharedBufferStack包含的BufferQueue内部都有三个Graphic Buffer,两个用于绘制一个用于显示.我们会把内容先绘制到一个后置缓冲区(OffScreen Buffer或者Back Buffer),在另外一个绘制下一帧;在需要显示时,才把离屏缓冲区的内容通过SwapBuffer驱动复制到Front Graphic Buffer中, 通过它将栅格化的信息交给SurfaceFlinger,SurfaceFlinger通过创建维护Layer再交给Hareware Composer,它会根据Layer中的位置、Z-Order顺序等信息合成为最终屏幕需要显示的内容,而这个内容会交给系统的帧缓冲区Frame Buffer来显示(Frame Buffer是非常底层的,可以 理解为屏幕显示的抽象).
(4)垂直同步VSYNC 60fps: 让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。如果下一帧周期到是屏幕前一帧没有绘制结束,后置缓冲区不能清空,多出来的一个后置缓冲区就可以用来绘制。
DisplayList:DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
Refresh Rate:屏幕在一秒时间内刷新屏幕的次数----有硬件的参数决定,比如60HZ.
Frame Rate:GPU在一秒内绘制操作的帧数,比如:60fps。
60fps:在与手机交换过程中,如果触摸及反馈 60帧以下人是能感觉出来的。60帧以上不能察觉变化。当低于60帧时感觉画面卡顿和迟滞现象
栅格化:将向量图形格式表示的图像转换成位图以用于显示器,即把button、textview等组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU的引入就是为了加快栅格化的操作。
过度绘制优化(主要减少GPU工作量)
-
过度绘制检测工具
开发者选项-》Debug GPU overdraw/调试GPU过度绘制
白色: 无过度绘制
蓝色: 过度绘制一次,正常一层背景和一层字体或按钮
淡绿: 过度绘制两次
淡红: 过度绘制三次
深红: 过度绘制四次或者四次以上
主要减少红色 -
GPU过度绘制几种情况:
(1). 自定义控件中 onDraw方法做了过多重复绘制
(2). 布局层次太深,重叠性太强。用户看不到的区域GPU也会渲染,导致耗时增加。
(3). View透明色属性即Alpha属性的使用,因为会在缓冲区渲染两次。 -
过度绘制优化:
(1) 减少背景重复:非业务需要,不要去设置背景
去掉所有activity主题设置中的默认背景
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="android:windowBackground">@null</item>
</style>
去掉单个activity的主题设置的属性:可以在setContentView之前getWindow().setBackgroundDrawable(null);
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray5"
android:orientation="vertical"
tools:context="com.lqr.wechat.activity.MainActivity">
<!--去掉android:background="@color/gray5" -->
去除和列表背景色相同的Item背景色;
若页面背景色与通用背景色不一致,在页面渲染完成后移除窗口和Decorview背景色;
当背景无法避免,但是某些情况下背景看不见,比如有图片时背景看得见,使用占位图时背景看不见,或者imageview同时存在有背景无图、无背景有图,设置图或者背景透明色Color.TRANSPARENT,因为透明色不渲染。
(2) 使用裁减减少控件之间的重合部分
clipRect()该方法用于裁剪画布,只会显示被裁剪的区域,之外的区域将不会显示。
该方法最后有一个参数Region.Op,表示与之前区域的区域间运算种类,如果没有这个参数,则默认为Region.Op.INTERSECT
这几个参数的意义为:
• DIFFERENCE是第一次不同于第二次的部分显示出来
• REPLACE是显示第二次的
• REVERSE_DIFFERENCE 是第二次不同于第一次的部分显示
• INTERSECT交集显示
• UNION全部显示
• XOR补集 就是全集的减去交集生育部分显示
注意:
clipxx方法只对设置以后的drawxx起作用,已经画出来的图形,是不会有作用的
优化前.jpg
优化代码.jpg
优化后.jpg
(3) 不在屏幕的元素尽量使用Canvas.quickReject把他们给剔除
(4) 占位View使用Space,CPU会计算,onDraw() 方法空不会交给GPU计算。
(5) TextView – 使用setTextColor()方法替代setAlpha()。这种方法使用Alpha通道来改变字色,字也会直接使用它进行绘制。
ImageView – 使用setImageAlpha()方法替代setAlpha()。原理同上。
自定义控件 – 如果你的自定义控件不存在过度绘制,那就开启硬件加速。
外边框都被切掉了,除非hasOverlappingRendering() 返回false。也可用view.getBackground.setAlpha(int int)设置透明度,则能够显示出正常边框
布局和View优化(主要减少CPU工作量)
卡顿原理分析:
当这一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件等待GPU完成栅格化渲染操作,这样会让这一帧画面,多停留16ms,甚至更多,这样就造成了画面看起来停顿。
- 布局检测工具
-
Android Device Monitor窗口中Hierarchy view
显示View的Measure, Layout和Draw花费时间。
三个点也是代表着View的Measure, Layout和Draw。
绿:表示该View的此项性能比该View Tree中超过50%的View都要快;例如,代表Measure的是绿点,意味着这个视图的测量时间快于树中的视图对象的50%。
黄:表示该View的此项性能比该View Tree中超过50%的View都要慢;
红:表示该View的此项性能是View Tree中最慢的
微信截图_20191028102549.png -
AndroidStudio的Tools中Layout Inspector
运行时检查其布局的详细信息。布局是在运行时构建的,不是完全用xml构建的,也会显示;方便对比分析和定位问题。
微信截图_20191028112805.png -
AndroidStudio的Tools中Layout Inspector
开发者选项-》Profile GPU rendering/分析GPU表现
Profile GPU renderin工具以滚动直方图的形式显示,相对于每帧16毫秒的基准,呈现ui窗口的帧需要多长时间。在功能较弱的gpu上,可用的填充率(GPU可以填充帧缓冲区的速度)可能非常低。随着绘制帧所需的像素数的增加,GPU可能需要更长的时间来处理新命令,并要求系统的其他部分等待,直到它能够赶上。分析工具可以帮助您识别GPU在试图绘制像素时是否被压垮,或者是否被过度渲染所累。
注意:此分析工具不适用于使用ndk的应用程序。这是因为每当OpenGL获取一个full-screen的context时,系统都会将框架消息推送到后台。在这种情况下,您可能会发现GPU制造商提供的分析工具很有帮助。
工具效果.PNG
Android6.0以上颜色说明.png
Android4.0到5.0颜色说明.png -
Systrace
Systrace是一个platform-provided工具,用于记录设备在短时间内的活动。 允许在系统级别上收集和检查设备上运行的所有进程的时间信息。将来自Android内核的数据(如CPU调度程序、磁盘活动和应用程序线程)结合起来生成一个HTML报告,帮助确定如何最好地提高应用程序或游戏的性能。该报告突出了它观察到的问题(如在显示动作或动画时的ui jank),并且提供了有关如何修复这些问题的建议。但是,Systrace不会在应用程序进程中收集有关代码执行的信息。可以通过自定义事件收集某个方法的执行时间包含native层,但是不显示具体执行信息包括执行了哪些函数。Java: Trace.beginSection("MyAdapter.onCreateViewHolder"); MyViewHolder myViewHolder; try { myViewHolder = MyViewHolder.newInstance(parent); } finally { // In 'try...catch' statements, always call `[endSection()](https://developer.android.google.cn/reference/android/os/Trace.html#endSection())` // in a 'finally' block to ensure it is invoked even when an exception // is thrown. Trace.endSection(); C: 为ATrace functions定义方法指针 #include <android/trace.h> #include <dlfcn.h> void *(*ATrace_beginSection) (const char* sectionName); void *(*ATrace_endSection) (void); typedef void *(*fp_ATrace_beginSection) (const char* sectionName); typedef void *(*fp_ATrace_endSection) (void); 在运行时加载ATrace符号,通常在对象构造函数中执行这个过程,出于安全原因,仅在应用程序或游戏的调试版本中包含对dlopen()的调用。 // Retrieve a handle to libandroid. void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL); // Access the native tracing functions. if (lib != NULL) { // Use dlsym() to prevent crashes on devices running Android 5.1 // (API level 22) or lower. ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>(dlsym(lib, "ATrace_beginSection")); ATrace_endSEction = reinterpret_cast<fp_ATrace_endSection>(dlsym(lib, "ATrace_endSection")); }
-
AndroidStudio--Profiler
提供实时数据帮助你理解你的APP是怎么样使用CPU、Memeory、Network、Battery Resources,主要看CPU分析界面一段时间内method如何调用和执行时间
Profiler.PNG - adb shell dumpsys gfxinfo
-
布局和View优化几种情况:
(1)布局过于复杂,无法在16ms内完成渲染
布局冗余、多层linearlayout嵌套、相对布局多次测量、业务复杂度高
(2)布局内容重复加载;listview和recycleview一直加载布局文件或者在onResume、onStart、onPostResume中加载布局
(3)离屏内容的绘制,Nav Drawer里面不可见的View系统会优化但不是所有,
(4)自定义View频繁的触发measure、layout,导致measure、layout累计耗时过多
(5)view频繁的重新渲染,包括频繁的设置可见与不可见方法,这样也会调用
(6)在主线程进行耗时操作,导致UI线程卡顿例如请求网络、IO读取数据
(7)内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作
频繁GC的原因:(1) 内存抖动(Memory Churn), 即大量的对象被创建又在短时间内马上被释放。(2) 瞬间产生大量的对象会严重占用 Young Generation 的内存区域, 当达到阀值, 剩余空间不够的时候, 也会触发 GC。即使每次分配的对象需要占用很少的内存,但是叠加在一起会增加 Heap 的压力, 从而触发更多的 GC。
(8)同一时间动画执行次数过多,导致CPU或者GPU负载过重
(9)View透明色属性即Alpha属性的使用,因为会在缓冲区渲染两次。
3.布局优化方案
(1)能在一个平面显示的内容,尽量只用一个容器,根据布局特点选择对应的布局,
尽可能少用wrap_content,wrap_content 会增加布局 measure 时计算成本,
尽量减少ConstraintLayout和RelativeLayout中太多无效依赖,可以减少不必要控件的刷新,
删除控件中无用的属性。
(2)使用merge尽可能把相同的容器合并,可以配合include使用
(3)删除冗余视图,比如TextView提供drawableLeft添加图片替代imageview
使用 SpannableStringBuilder替换多个texview,多种不同大小、颜色或者图文混排需要显示时,我们往往会利用多个TextView来进行组合,但是某些效果通过一个TextView就可以实现。
Android中各种Span的用法
使用LinearLayout自带的divider属性实现分割线,而不是在布局中手动添加一个额外的View作为分割线
与分割线相关的属性包括以下几个:
divider:传入分割线的drawable,可以是一个图片,也可以是自己通过xml实现的drawable。
showDividers:分割线显示的位置,beginning/middle/end,分割对应头部、中间、尾部。
dividerPadding:分割线距离两边的间距
(4)ListView和Recycleview,可以重复使用item,注意不要在更新内容的地方加载布局,局部刷新如 RecyclerView 的 DiffUtil
(5)代码上能复用View的尽量复用,延时加载或者普通加载XML布局不要在反复调用的方法中使用
(6)对于业务复杂的布局可以使用延时加载的地方尽量使用,比如ViewStub,业务上要减少非必须业务控件,以及减少对离屏内容的绘制
(7)减少对requestlayout和invalidate()频繁调用,View还可以调用局部刷新方法invalidate(int left, int top, int right, int bottom) ,自定义view要减少对子view的重复measure ,不要做复杂的绘图操作,重复内容需要剪裁,
(8)不要在onMeasure、onLayout和onDraw和activity声明周期方法中做耗时操作,尽量放到子线程。
(9)用 SurfaceView 或 TextureView 代替普通 View。SurfaceView 或 TextureView 可以通过将绘图操作移动到另一个单独线程上提高性能
(10) 尽量为所有分辨率创建资源,减少不必要的硬件缩放,这会降低 UI 的绘制速度。这个和APK大小之间要做取舍。
(11)space 经常用于组件之间的缝隙,其draw()为空,Space 相对于View设置间距的好处是不用draw,缺点是不能设置背景增加了布局中的View。
(12)尽量少在onMeasure、onLayout、onDraw和for循环中创建对象,防止频繁GC
(13) 合理使用动画,尽量使用属性动画,减少了自身的重绘。某些情况下可以用硬件加速方式来提供流畅度,或者采用自定义view代替动画,最后记得在Activity的onStop()方法中调用Animation.cancle()进行动画停止。
启用硬件加速
Application 级别
<application android:hardwareAccelerated="true" />
Activity 级别
<activity android:hardwareAccelerated="true" />
Window 级别
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View 级别
// 如果是 software,会将 View 绘制到一个 Bitmap,
// 然后依然是通过硬件加速将 Bitmap 绘制到 Canvas
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
LAYER_TYPE_NONE:普通渲染方式,不会返回一个离屏的缓冲,默认值。
LAYER_TYPE_HARDWARE:如果这个应用使用了硬件加速,这个 View 将会在硬件中渲染为 硬件纹理,
如果应用程序并没有被硬件加速,则其效果和 LAYER_TYPE_SOFTWARE 相同。
LAYER_TYPE_SOFTWARE:此 View 通过软件渲染为一个 Bitmap。
检查是否开启了硬件加速
view.isHardware-Accelerated();
Canvas.isHardwareAccelerated();
如果 View 中要处理中文长文本,需要关闭硬件加速。因为每个中文编码不一样,效果不理想。
(14)RenderThread与RenderScript
在Android 5.0,系统增加了RenderThread,对于ViewPropertyAnimator和CircularReveal动画,我们可以使用RenderThead 实现动画的异步渲染。当主线程阻塞的时候,普通动画会出现明显的丢帧卡顿,而使用RenderThread渲染的动画即使阻塞了 主线程仍不受影响.
现在越来越多的应用会使用一些高级图片或者视频编辑功能,例如图片的高斯模糊、放大、锐化等.拿 "扫一扫"这个场景来看,这里涉及大量的图片变换操作,例如缩放、裁剪、二值化以及降噪等. 图片的变换涉及大量的计算任务,根据我们说到的,这个时候使用GPU是更好的选择. 我们可以通过RenderScript,它是Android操作系统上的一套API.它基于异构计算思想,专⻔用于密集型计算.
RenderScript 提供了三个基本工具:一个硬件无关的通用计算API;一个类似于CUDA、OpenCL和GLSL的计算API;一个类C99的脚本语 言.允许开发者以较少的代码实现功能复杂且性能优越的应用程序
16dc962f1ba3a91c.png
-
总结:
性能优化其实不仅仅是一种技术,而是一种思想,你只听过它的高大上,却不知道它其实就是各个细节处的深入研究和处理。就像挤牙膏一样。
网友评论