android 性能优化之布局优化

布局是一个App非常关键的一部分,布局性能的好坏可直接影响到用户的体验;试想下如果一个RecyclerView滑动时异常卡顿,那用户估计也没有心情去住下滑了,可能就直接强制杀掉App了去,然后回过头去应用商店给个差评“卡的不要不要的”

虽然现在手机内存、CPU等各项性能都上来了看起来很强,实际上还是卡的不行,所以我们还是要多学习下这方面的知识

「Android性能优化」一些你需要知道的布局优化技巧

布局优化

减少布局层级

● 布局层级越高越复杂消耗性能就越大,所以在设计布局时尽量缩小布局层级数

viewgroup性能

● 常用的像LinearLayout、FrameLayout、RelativeLayout等,其中RelativeLayout消耗性能最多,所以这里能使用LinearLayout或FrameLayout的时候尽量使用这两种

● 如果2种都需要同时使用,则RelativeLayout性能会好于两种结合的布局形式

总结起来就是:

● LinearLayout = FrameLayout < RelativeLayout

● LinearLayout + FrameLayout > RelativeLayout

标签用法

● 在xml布局中,可以使用标签也可以优化布局,比如,, 以及viewstub的使用等

为什么要进行布局优化?

如果布局嵌套过深,或者其他原因导致布局渲染性能不佳,可能会导致应用卡顿

绘制优化

ondraw方法避免耗时

● 在ondraw方法中不要使用new等创建对象操作,ondraw方法会执行多次,因此会导致多次重复创建对象,占用系统资源

● 在ondraw中不要进行耗时操作,如ondraw方法名,此方法用于绘制,其他耗时操作可在其他方法中进行

PS:流畅的绘制为60fps,也就是在ondraw方法中每帧绘制不超过大概16ms左右

Android绘制原理

Android的屏幕刷新中涉及到最重要的三个概念

CPU: 执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU

GPU: 进一步处理数据,并将数据缓存起来

屏幕: 由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点

总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示

「Android性能优化」一些你需要知道的布局优化技巧

双缓冲机制

看完上面的流程图,我们很容易想到一个问题,屏幕是以16.6ms的固定频率进行刷新的,但是我们应用层触发绘制的时机是完全随机的(比如我们随时都可以触摸屏幕触发绘制)

如果在GPU向缓冲区写入数据的同时,屏幕也在向缓冲区读取数据,会发生什么情况呢?

● 有可能屏幕上就会出现一部分是前一帧的画面,一部分是另一帧的画面,这显然是无法接受的,那怎么解决这个问题呢?

● 所以,在屏幕刷新中,Android系统引入了双缓冲机制

GPU只向Back Buffer中写入绘制数据,且GPU会定期交换Back Buffer和Frame Buffer,交换的频率也是60次/秒,这就与屏幕的刷新频率保持了同步

「Android性能优化」一些你需要知道的布局优化技巧

虽然我们引入了双缓冲机制,但是我们知道,当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理

● 当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定

● 如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换

这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧

布局层级优化方式

使用ConstraintLayout减少布局嵌套,层级不要太深,使用merge标签等

减少主线程布局创建时间的方式:

● 使用AsynclayoutInflater异步创建布局,这种方式是把耗时操作放到了子线程,但是会缺少TextView -> AppCompatTextView等兼容效果

● 使用纯代码的方式编写布局,new View(context),复杂、繁琐,不好维护

● 使用第三方库如X2C等,使用xml编写布局,在编译时转成Java代码,这种方式同样缺少TextView -> AppCompatTextView等兼容效果,且有些xml属性不支持

● 使用Jetpack Compose,声明式UI,应该是未来的趋势了

● 减少过度绘制

● 减少不必要的背景设置,减少复杂shape等

自定义View的时候,使用Canvas的clipRect方法避免绘制被遮盖的内容

布局加载原理

由上面可知,导致掉帧的原因是CPU无法在16.6ms内完成绘制数据的计算;而之所以布局加载可能会导致掉帧,正是因为它在主线程上进行了耗时操作,可能导致CPU无法按时完成数据计算

布局加载主要通过setContentView来实现

● 在setContentView中主要有两个耗时操作

● 解析xml,获取XmlResourceParser,这是IO过程

● 通过createViewFromTag,创建View对象,用到了反射

以上两点就是布局加载可能导致卡顿的原因,也是布局的性能瓶颈

获取布局文件加载耗时的方法

我们如果需要优化布局卡顿问题,首先最重要的就是:确定定量标准 所以我们首先介绍几种获取布局文件加载耗时的方法

布局加载慢的主要原因有两个,一个是IO,一个是反射 所以我们的优化思路一般有两个

● 侧面缓解(异步加载)

● 根本解决(不需要IO,反射过程,如X2C,Anko,Compose等)

方案实操

AsyncLayoutInflater方案

AsyncLayoutInflater 是来帮助做异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束之后 OnInflateFinishedListener 会在主线程回调返回 View;这样做旨在 UI 的懒加载或者对用户操作的高响应

● 简单的说我们知道默认情况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的

● AsyncLayoutInflater 就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应

使用如下:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n381" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); new AsyncLayoutInflater(AsyncLayoutActivity.this) .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished(View view, int resid, ViewGroup parent) { setContentView(view); } }); // 别的操作}</pre>

这样做的优点在于将UI加载过程迁移到了子线程,保证了UI线程的高响应 缺点在于牺牲了易用性,同时如果在初始化过程中调用了UI可能会导致崩溃

X2C方案

X2C是掌阅开源的一套布局加载框架 它的主要是思路是在编译期,将需要翻译的layout翻译生成对应的java文件,这样对于开发人员来说写布局还是写原来的xml,但对于程序来说,运行时加载的是对应的java文件;这就将运行时的开销转移到了编译时;如下所示

原始xml文件:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n386" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="***/apk/res/android" xmlns:app="***/apk/res-auto" xmlns:tools="***/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="10dp"> <include android:id="@+id/head" layout="@layout/head" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizOntal="true" /> <ImageView android:id="@+id/ccc" style="@style/bb" android:layout_below="@id/head" /></RelativeLayout></pre>

X2C 生成的 Java 文件

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n388" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class X2C_2131296281_Activity_Main implements IViewCreator { @Override public View createView(Context ctx, int layoutId) { Resources res = ctx.getResources(); RelativeLayout relativeLayout0 = new RelativeLayout(ctx); relativeLayout0.setPadding((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,res.getDisplayMetrics())),0,0,0); View view1 =(View) new X2C_2131296283_Head().createView(ctx,0); RelativeLayout.LayoutParams layoutParam1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT); view1.setLayoutParams(layoutParam1); relativeLayout0.addView(view1); view1.setId(R.id.head); layoutParam1.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE); ImageView imageView2 = new ImageView(ctx); RelativeLayout.LayoutParams layoutParam2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,res.getDisplayMetrics()))); imageView2.setLayoutParams(layoutParam2); relativeLayout0.addView(imageView2); imageView2.setId(R.id.ccc); layoutParam2.addRule(RelativeLayout.BELOW,R.id.head); return relativeLayout0; }}</pre>

使用时如下所示,使用X2C.setContentView替代原始的setContentView即可

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n390" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// this.setContentView(R.layout.activity_main);X2C.setContentView(this, R.layout.activity_main);</pre>

X2C优点

● 在保留xml的同时,又解决了它带来的性能问题

● 据X2C统计,加载耗时可以缩小到原来的1/3

X2C问题

● 部分属性不能通过代码设置,Java不兼容

● 将加载时间转移到了编译期,增加了编译期耗时

● 不支持kotlin-android-extensions插件,牺牲了部分易用性

尾述

如果你对于性能问题不能由点及面逆向分析,最终找到瓶颈点和优化方法,那么必须要跟着正确的路线学习!

所以在这里分享一张由大佬收集整理的 Android 性能优化学习思维导图

「Android性能优化」一些你需要知道的布局优化技巧

并且通过这张思维导图融合了这些年的工作经历及对网上的资料查询和整理,最终将其整合成了一份 Android 性能优化的学习手册文档 ;有需要这份 思维导图及学习手册文档的朋友:可在评论区下方留言,或者私信发送 “脑图”“进阶” 即可 免费获取

希望大家通过这个思维导图和学习手册,能够提供一个好的学习方向查漏补缺完善自身的不足之处;早日攻克性能优化这一难题

手册内容展示如下:

App 性能优化-启动优化

「Android性能优化」一些你需要知道的布局优化技巧

App 性能优化-UI 布局优化

「Android性能优化」一些你需要知道的布局优化技巧

Android 性能优化-卡顿优化和布局优化

「Android性能优化」一些你需要知道的布局优化技巧

Android 开发-优化 Glidel 加载超大 gif 图

「Android性能优化」一些你需要知道的布局优化技巧

以上的知识点文档都是免费获取的,有兴趣的小伙伴:可在评论区下方留言,或者私信发送 “脑图”“进阶” 即可 免费获取觉得手册内容有用的话,大家可以点赞分享一下

PS:有问题欢迎指正,可以在评论区留下你的建议和感受; 欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:dandanxi6@qq.com

(0)
上一篇 2023年 1月 9日 上午9:40
下一篇 2023年 1月 9日 上午10:02

相关推荐

  • 微信解封的六种方法,微信账号解封最快方法

    微信被限制了怎么办?解决的办法有哪些?平时该怎么规避?今天,在这里讲讲其中的知识和汇总所有的解决办法,供于大家参考。 方法一:辅助解除 页面中显示可解封,只要完成辅助流程即可,这是…

    2023年 6月 17日
  • 历代iphone的缺点你知道几个

    随着时代的发展、科技的进步,手机早已成为了我们日常生活中的必需品。而在2007年1月9日,苹果公司在美国旧金山马士孔尼会展中心召开了Macworld大会,会上苹果公司的首席执行官史…

    2023年 3月 9日
  • 微信支付的密码怎样查看

    微信支付原密码在哪里查看呢?答案:无法直接获取原始的支付密码!不过用户通过“忘记支付密码”进行找回。出现这种情况是因为支付密码与金融相关,属于安全等级最高的范畴。微信作为第三方交易…

    2022年 12月 24日
  • oppo r11第一次官方亮相, 前后2000万像素引人遐想

    今天,OPPO正式发布全新拍照旗舰OPPO R11,作为国产最成功的系列之一,OPPO R系列创造了很多的记录,尤其是去年的R9/R9s。站在巨人肩膀上,OPPO R11应该会看得…

    2023年 4月 9日
  • 打麻将合伙碰牌暗号,发现后可以报警吗

    (记者 陶 琛)在麻将馆里,他们使用“暗号”打合手牌、“带笼子”,所向披靡,两个月里骗得他人财物共计2.3万余元。日前,湖南省汨罗市人民法院公开审理此案,被告人张某、许某犯诈骗罪,…

    数码教程 2023年 3月 23日
  • 华为荣耀手机有些app安装不上的解决方法

    荣耀手机有些app装不上的解决办法。 近来,有好多朋友都遇到荣耀手机,市场没有的app浏览器或者微信下载安装不上的问题,相信大多数人不会为了个app而去换手机, 刚好我也遇到了,就…

    数码教程 2023年 9月 9日
  • 苹果5s手机屏幕耐摔吗

    更多热门文章: 赞!魅族PRO 7终极一曝:7月26日发布,或2799元起售 S8沉默!小米全面屏新机曝光:或为红米Pro 2,颜值飙升! 对标高通!华为麒麟970曝光:CPU架构…

    2023年 3月 23日
  • 手机运行内存8g为什么清理完可用就3个多g

    有不少小伙伴可能都遇到过这种问题,手机使用起来还算流畅,但手机一直提示内存不足。迫于无奈之下有些小伙伴可能就会选择删除一些不常用的APP以及视频、照片之类的,以保证手机的内存充足。…

    2022年 12月 31日
  • 折叠手机屏故障:机主不认可系人为损坏,华为称可减免八百维修费

    华为折叠手机P50 pocket。视觉中国 图 近日,江苏连云港的卞女士向澎湃质量观投诉平台(***)反映,她购买的华为p50pocket折叠屏手机使用半年之后折叠处出现粉色斑点,…

    2023年 3月 3日
  • 手机锁屏密码忘了怎么解开不清除数据

    虽然我们每天都在使用自己的手机,看起来忘记锁屏密码这种事情不太可能发生,但事实上,经常使用指纹解锁和面部解锁的你真的记得自己的手机锁屏密码吗? 最近有朋友在私信小编表示自己的手机锁…

    2023年 9月 11日