网页客服,欢迎咨询

Android页面渲染效率优化实践

2025-01-06 11:00:01 66
  • 收藏
  • 管理
    总篇196篇 2023年第9篇1.车系页布局渲染现状?车系页是重要的车系信息页面,更新迭代多年,页面布局不断变化,xml布局文件越写越复杂。获取车系页布局文件耗时:startTime?=?System.currentTimeMillis();setContentView(R.layout.car_series_revision_activity);long?durTime?=?System.currentTimeMillis()?-?startTime;LogHelper.e("布局总耗时","车系页布局耗时:"?+?durTime);结果如下:2.卡顿的原因2.1Android绘制原理1.Android的屏幕刷新中涉及到最重要的三个概念(1)CPU:执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU(2)GPU:进一步处理数据,并将数据缓存起来(3)屏幕:由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。2.双缓冲机制当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理。当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定。如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧。2.2布局加载原理页面启动时,布局加载在主线程上进行耗时操作,会导致页面渲染及加载慢。布局加载主要通过setContentView来实现,下面是它的调用时序图:我们可以看到,在setContentView中主要有两个耗时操作:(1)解析xml,获取XmlResourceParser,这是IO过程。(2)通过createViewFromTag,创建View对象,用到了反射。以上两点就是布局加载慢的原因,也是布局的性能瓶颈。3.布局加载优化上一章分析了布局加载慢的主要原因,因此,我们的优化方式主要有以下两种:(1)异步加载,将布局加载过程转移到子线程(2)去掉IO和反射过程3.1异步加载,AsyncLayoutInflater方案?setContentView 默认是在UI主线程加载布局的,其加载过程中的耗时操作,如解析xml,反射创建view对象等也是在主线程执行,AsyncLayoutInflater 可以让这些加载过程在子线程中执行,这样可以提高UI线程的响应性,UI线程同时可以进行其他操作。AsyncLayoutInflater使用方式如下:new?AsyncLayoutInflater(this).inflate(R.layout.car_series_revision_activity,?null,?new?AsyncLayoutInflater.OnInflateFinishedListener()?{@Overridepublic?void?onInflateFinished(@NonNull?View?view,?int?resid,?@Nullable?ViewGroup?parent)?{setContentView(view);}});AsyncLayoutInflater方案的缺点:(1) UI布局和view的初始化在子线程中进行,如果view还未初始化成功,在主线程中再调用view会引起崩溃。(2) 一般情况下,主线程会调用view,涉及到大量子线程和主线程在view调用上的同步问题,这就牺牲了易用性,代码可维护性也会变差。(3) 如果是在老页面逻辑结构上引入AsyncLayoutInflater进行改造,结构改动很大,很容易发生view调用崩溃错误,不太可行。3.2X2C方案?X2C 是掌阅开源的一套布局加载框架。X2C的主要思路是利用apt工具,在编译期将我们写的xml布局文件解析成view,并根据xml动态设置view的各类属性,这样,我们在运行时,调用findViewById,根据view id拿到的view,已经是直接new 出来的view,避免了运行时的xml IO操作和反射操作,这就解决了布局时的耗时问题。原始的xml布局文件:X2C 编译期apt生成的java文件:public?class?X2C127_Activity?implements?IViewCreator?{@Overridepublic?View?createView(Context?ctx)?{?Resources?res?=?ctx.getResources();LinearLayout?linearLayout0?=?new?LinearLayout(ctx);linearLayout0.setTag(R.id.x2c_rootview_width,ViewGroup.LayoutParams.MATCH_PARENT);linearLayout0.setTag(R.id.x2c_rootview_height,ViewGroup.LayoutParams.MATCH_PARENT);linearLayout0.setId(R.id.constraintLayout);linearLayout0.setGravity(Gravity.CENTER);linearLayout0.setOrientation(LinearLayout.VERTICAL);Button?button1?=?new?Button(ctx);LinearLayout.LayoutParams?layoutParam1?=?new?LinearLayout.LayoutParams((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,150,res.getDisplayMetrics())),(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50,res.getDisplayMetrics())));button1.setBackgroundColor(res.getColor(R.color.colorAccent));button1.setTextSize(TypedValue.COMPLEX_UNIT_DIP,20);button1.setGravity(Gravity.CENTER);button1.setTextColor(Color.parseColor("#ffffff"));button1.setId(R.id.x2c);button1.setText("X2C");button1.setLayoutParams(layoutParam1);linearLayout0.addView(button1);return?linearLayout0;}}X2c的优点:(1)易用性和可维护性好,对原有代码侵入性不强,应用代码还是使用xml写布局(2)加载耗时可缩短到原来的1/2到1/3X2c的缺点:(1)View的属性支持不完全(2)兼容性和稳定性不是很高,在高版本的gradle 编译工具,如gradle3.1.4,会出现找不到R.java文件,找不到xml对应的java文件等问题(3)目前,X2C更新到2021年,并没有持续维护和解决issue3.3Compose方案Compose 是 Jetpack 中的一个新成员,是 Android 团队在2019年I/O大会上公布的新的UI库。Compose使用纯kotlin开发,使用简洁方便,但它是完全抛弃了View 和 ViewGroup这套系统,自己把整个的渲染机制从里到外做了个全新的,是未来取代XML的官方方案。Compose的优点:(1)使用声明式UI,摒弃了xml布局运行时解析,布局效率更高(2)使用kotlin开发,简单易用,布局形式上跟flutter统一。如果是使用kotlin开发的新项目,可以引入Compose方案,对于老项目的优化,Compose方案并不适用。3.4我们的优化方案-在布局反射上做文章?Xml解析到view,完全自己来做,比较复杂且有很多风险,这个过程涉及到两个耗时的点:(1)xml解析,IO操作(2)反射xml解析这部分工作复杂度很高,可以交给android系统来做。我们可以想办法去除反射的逻辑。我们需要找到一个反射生成view的入口。我们知道,View生成相关逻辑在LayoutInflater的createViewFromTag中,调用了onCreateView(parent, name, context, attrs),通过反射生成了view。通过android系统的LayoutInflater setFactory,我们不仅可以控制View的生成,还可以把View变成另外一个View。在setFactory的onCreateView(parent, name, context, attrs)回调中,我们接管单个view的生成,去掉反射,new 出我们自己的view就解决了问题。而onCreateView(parent, name, context, attrs)中的参数name返回的就是xml中使用到的view的名字,根据这个name,直接new出来新的view。方式如下:LayoutInflaterCompat.setFactory(getLayoutInflater(),?new?LayoutInflaterFactory()?{@Overridepublic?View?onCreateView(View?parent,?String?name,?Context?context,?AttributeSet?attrs)?{switch?(name)?{case?"TextView":return?new?TextView(context,?attrs);case?"ImageView":return?new?ImageView(context,?attrs);case?"com.cubic.choosecar.ui.car.view.NewStarView":return?new?com.cubic.choosecar.ui.car.view.NewStarView(context,?attrs);case?"com.cubic.choosecar.ui.carseries.scrolllayout.ScrollableLayout":return?new?com.cubic.choosecar.ui.carseries.scrolllayout.ScrollableLayout(context,?attrs);case?"View":return?new?View(context,?attrs);case?"com.cubic.choosecar.newui.carseries.view.CarRevisionSeriesImageScaleLayout":?//自定义viewreturn?new?com.cubic.choosecar.newui.carseries.view.CarRevisionSeriesImageScaleLayout(context,?attrs);case?"ViewStub":return?new?ViewStub(context,?attrs);case?"ScrollView":return?new?ScrollView(context,?attrs);case?"androidx.constraintlayout.widget.ConstraintLayout":return?new?androidx.constraintlayout.widget.ConstraintLayout(context,?attrs);case?"FrameLayout":return?new?FrameLayout(context,?attrs);case?"RelativeLayout":return?new?RelativeLayout(context,?attrs);case?"androidx.appcompat.widget.Toolbar":return?new?androidx.appcompat.widget.Toolbar(context,?attrs);case?"LinearLayout":return?new?LinearLayout(context,?attrs);default:View?view?=?getDelegate().createView(parent,?name,?context,?attrs);return?view;}//return?view;}});包括系统view和我们自定义的view。此方案对已有项目的代码侵入性很小,改造成本低,兼容性也很高,相对来讲,在渲染效率上比X2C方案低一些,但比较匹配我们对已有旧项目复杂布局的渲染优化。?3.5进一步在布局上优化?我们可以使用viewStub实现布局的懒加载。思路是将布局分成不同的模块,让部分模块使用viewStub标签替代,一半屏幕的模块元素渲染完成以后,再通过viewStub来渲染生成viewStub所包含的其它模块,实现延迟渲染加载。通过分析车系页布局,已经将布局元素,按功能做了一些模块的划分,我们进一步将关联度大的布局模块集中在一起,封装在一个自定义VIEW中,使用viewStub包含替换这些模块View。UI线程setContentView渲染布局时,viewStub所包含的模块并不会被渲染,只会渲染屏幕的部分元素,等待主接口数据返回,再使用viewStub延迟其它模块,实现了布局的懒加载,加快了主线程的渲染速度。?4.优化结果通过3.4和3.5节的优化方法,车系页复杂布局渲染优化对比结果如下:通过对比可以看到,在不同档次的android机型上,渲染耗时降低了20%-35%左右,在低端机型上,减少的绝对耗时更多,感受可能会明显一些。作者简介蒋雄锋经销商事业部-经销商技术部。2018年加入汽车之家,目前任职经销商技术部移动App团队,主要涉及Android移动端,Flutter,React Native等大前端技术,负责汽车报价App业务的开发。阅读更多:TiDB数据库在汽车之家的应用与实践服务模块化问界低代码平台架构设计及业务实践 Android页面渲染效率优化实践
    上一页:ChatGPT技术产品落地实践:从技术架构到实际应用 下一页:Android应用安全开发之组件安全浅谈
    全部评论(0)