关于android:跨越适配性能那道坎企鹅电竞Android-weex优化

导读

企鹅电竞从17年6月接入weex,到当初曾经有一段时间了,这段时间外面,针对遇到的问题,企鹅电竞终端次要做了上面的优化:
image组件
预加载
预渲染

Image组件
weex的list组件和image组件非常容易出问题,企鹅电竞自身又存在很多有限列表的weex页面,list和image的组合暴发的内存问题,导致接入weex后app的内存问题导致的crash始终居高不下。

list组件问题
首先来说一下list,list对应的实现是WXListComponent,对应的view是BounceRecyclerView。RecyclerView应该大家都很相熟,android support库外面提供的高性能的代替ListView的控件,它的存在就是为了列表中元素复用。原本weex应用了RecyclerView作为list的实现,是一件大快人心的事件,然而RecyclerView中有一种使用不当的状况,会导致view不可复用。
下图形容了RecyclerView的复用流程:当初的RecylerView导致的软件品质降落的问题急需在列表中展现解决

[ RecyclerView复用 ]

weex中的RecyclerView并没有设置stableId,所以RecyclerView的所有复用都依赖于ViewHolder的ViewType,Weex的ViewType生成见下图:

在没有设置scope的状况下,viewHolder的component的ref就是viewType,即所有的ViewHolder都是不同且不可复用的,此时的RecyclerView也就进化成了一个略微简单一点的ScrollView。

如果设置了scope属性,但你相对想不到,scope自身也是一个坑。上面间接上代码:

下面代码中,能够看到,应用了scope,当复用Holder时,会把须要展现的component的数据绑定到复用的component中。那么问题来了,如果我不是只是想批改局部属性,而是须要扭转component的层级关系呢?例如从a->b->c批改成a->c->b,那么是不是只能用不同的viewType或者是说变成上面的构造:a->b a->c b->b1 b->c1 c->c2 c->b2这样的构造,然而view的实例多了,必然又会导致内存等各种问题。最为致命的问题是,createViewHolder的时候,传给ViewHolder的component实例就是原件,而非拷贝,当bindData执行了当前,就等用于你复用的那个component的数据被批改了,当你再滑回去的时候,GG。

所以scope属性根本不可用,留给咱们的只有相当于scrollView的list。

还好,为了解决list这么戳的性能,有了recyclerList,从vue的语法层,反对了模板的复用。然而坑爹的是,0.17 、 0.18 版本recyclerList都有这样那样的问题,重构同学感觉应用起来效率较低。0.19版本weex团队fix了这些问题后,企鹅电竞的前端同学也正在尝试往recyclerList去切换。

image组件问题
置信android开发们都分明,图片的问题永远是大问题。OOM、GC等性能问题,常常就是随同着图片操作。

在0.17版本以前,WXImageView中bitmap的开释都是在component的recycle中执行,0.17版本之后,在detach时也会执行recycle,然而WXImageView的recycle只是把ImageView的drawable设置为null,并没有理论调用bitmap的recycle。

而企鹅电竞在版本运行过程中发现,仅仅把bitmapDrawable设置为null,不去调用bitmap的recycle,局部机型下面的oom问题十分突出(这里始终没想明确,为啥这部分机型会呈现这个问题,前面替换成fresco去治理就没这个问题了)。当然,如果间接recycle bitmap,不设置bitmapDrawable,会间接导致crash。

回到企鹅电竞自身,企鹅电竞中的图片治理应用了fresco,在接入weex以前,咱们曾经针对fresco加载图片做了一系列优化,而且fresco自身曾经蕴含了三级缓存等性能。

接入weex后,首先想到的就是应用fresco的管线加载出bitmap后给WXImage应用。在这个过程中,先是遇到了对CloseableReference治理不失当导致bitmap 还在应用却被recycle 掉了,而后又遇到了没有执行recycle导致bitmap无奈开释的坑。在长列表中,图片无奈开释的问题被有限放大,经常出现疾速滑动几屏就oom的问题。而且随着业务倒退应用WXImage无奈播放gif和webp图片也成为瓶颈。

后续版本中,企鹅电竞间接重写了image和img标签,应用Fresco的SimpleDraweeView替换了ImageView。该计划带来的收益是bitmap不在须要本人治理,即oom问题和bitmap recycle之后导致的crash问题会大大减少,且fresco默认就反对gif和webp图片。然而,这个计划也有个致命的问题:圆角。

圆角问题得先从fresco和weex各自的圆角计划说起。
weex圆角(盒模型-border):https://weex.apache.org/cn/wi…
fresco圆角:https://www.fresco-cn.org/doc…

fresco圆角计划具体可见RoundedBitmapDrawable,RoundedColorDrawable,RoundedCornersDrawable这3个类,fresco圆角属性的扭转最终都只是批改这3个类的属性,圆角也是基于draw时候批改canvas画布内容实现,BtimapDrawable的裁减以及边框的绘制都是在draw的时候绘制下来。

weex圆角计划具体可见ImageDrawable,实现计划为借助android的PaintDrawable,通过设置shader实现bitmapDrawable的裁减,然而边框的绘制则依赖于backgroundDrawable。

而且在fresco中,封装了多层的drawable,较难批改drawabl的 draw的逻辑,而且边框参数的设置也不如weex众多样化。

针对两者的差异性,企鹅电竞的解决方案是放弃fresco的圆角计划,通过fresco的后处理器裁减bitmap达到圆角的成果,边框复用weex的background的计划。这个计划惟一的问题后处理器中必须创立一份新的bitmap,然而通过复用fresco的bitmapPool,并不会导致内存有过多的问题。

上面贴一下后处理器解决圆角的要害代码:

当list和image组合在一起的时候,因为weex的image并没有recycle掉bitmap,而且没有bitmapPool的应用,会导致长列表weex页面占用内存特地高。而替换为fresco的bitmap内存管理模式后,因为weex导致的内存crash问题占比显著从最开始版本的2%降落到了0.1%-0.2%。

预加载
当踩完大大小小的坑,缓解了内存和crash问题之后,企鹅电竞在weex应用上又遇到了2大难题:
1. 调试艰难
2. 页面加载慢

调试艰难
weex的页面并不能给前端的开发同学丝滑的调试体验。最开始前端同学是采纳终端日志或者弹框的形式调试(疼爱前端同学就这么学会了看android日志),前面通过再三跟weex团队的沟通,终于确定了weex和weex_debuger对应的版本,前端同学能够在chrome下面调试weex页面。

然而weex_deubgger并不是完满的解决方案,weex自身是jscore内核,而weex_debugger只是通过chrome调试协定开了个服务,等同于应用的是chrome的内核,内核的不一致性无奈保障调试的准确性。连weex的开发同学本人都说了会遇到debug环境和正式环境后果不统一的状况。

解决方案也很简略,那就是能够在mac的xcode和safari下面调试。过后因为替换mac的胜利过高,就将就应用了weex_debugger的计划,前面怎么解决了置信大家心里有数。

页面加载速度慢
随着企鹅电竞业务的倒退,很快前端同学就反馈过去,怎么weex页面关上的速度这么慢,这个菊花转了这么久。过后的心田是解体的,明明接入的时候好好的,一个页面轻轻松松500-600ms就加载回来了,哪里会有问题?

业务的倒退速度永远是你设想不到的,2个版本不到的工夫,企鹅电竞中的weex页面轻轻松松从个位数冲破到两位数,bundle大小也轻轻松松从几十kb冲破到了上百kb,由此带来的问题是关上weex页面后能显著看到菊花转动了,甚至关上速度上还不如直出的web页面。

首先从数据报表中发现,页面关上速度中,1s中有300-400ms是bundle从网络下载的工夫,那是不是把这段时间省了,页面有轻轻松松回到毫秒级别关上速度了。

下图展现了预加载的整体流程。

[ 预加载流程 ]

预加载计划上线后,页面胜利节俭了将近200ms的耗时。20M的LRUCache大小也是参考了http cache的默认大小值,页面关上的预加载率在75%-80%。

预渲染
做了预加载之后,很快又发现,就算没有网络申请,页面关上耗时还是超过了1s。这种状况下,现有的计划曾经无奈持续优化页面。这个时候忽然有了个想法,weex自身是把前端的虚构dom转化为终端的各种view控件,那么为什么weex页面的关上会慢终端页面关上这么多呢?
定义问题
解决问题之前,先来定义一下问题具体是什么。针对渲染速度慢,企鹅电竞对weex渲染的耗时定义如下:
· renderStart = 调用WXSdkInstance.render()的工夫点
· httpFinish = httpAdapter申请回来之后调用WXSdkInstance.onHttpFinish()的工夫点
· renderFinish = 回调 IWXRenderListener.onRenderSuccess()的工夫点
· 页面关上耗时 = renderFinish – renderStart
· 网络耗时 = httpFinish – renderStart
· 渲染耗时 = renderFinish – httpFinish

所以之前的预加载,曾经优化了网络耗时,然而渲染耗时在页面大了之后,依旧会有很大的性能问题。
为了揭开这个问题的实质,先来看一下weex整体的框架:

[ weex框架图: ]

JSFrameWork
提供给前端的sdk,对vue的dom操作做了各种封装,JSFrameWork独自打包到apk包中。

JavaScriptCore
应用与safari的JavaScript引擎,专门解决JavaScript的虚拟机,对应chrome的v8,性能能够大体联想成java的jvm。

JSS
weex core的server端,封装了对JavaScripteCore的调用,封装了instance的沙盒,多过程实现中,JSS和JavaScriptCore的执行在另外的过程,避免JS执行异样导致主过程解体。

JSC
weex core的client端,作为WeexFrameWork和JSS桥接层,另外从0.18版本开始,cssLayout也下沉到了这一层。

WeexFrameWork
提供各种sdk接口的java调用,虚构dom和Android控件树的转换,控件治理等。

理解完了weex框架,再把关注点转移到js build之后生成的jsBundle,仔细的同学必定可能发现,生成的jsBundle实质上就是一个js办法,所以weex页面render的过程实质上是执行一个js办法。当初的Js方法论曾经不适用于大多数的状况
针对企鹅电竞关注的游戏首页,对整个weex框架加了残缺的打点,看到在nexus 6下面,对应的耗时以及整体流程如下图:

[ weex执行流程以及耗时 ]

能够看到性能的热点次要在执行js办法以及虚构dom的执行这两个关键步骤上,依据打点来看,单个js办法和单个虚构dom的执行,耗时都很低。企鹅电竞抓了屡次打点,看到启动时候执行js最慢的也仅仅是3ms,大多数执行都在0.1ms – 0 ms这个区间。然而,再快的执行耗时,也架不住量多,同样以企鹅电竞游戏首页为例,启动的时候该页面执行的js办法多大2000+个,这2000+个办法执行再加上办法调度的耗时,能成为性能热点一点也不意外。而虚构dom的执行也同理,单次执行通过weex团队的优化,执行耗时根本在1ms-3ms之间,然而同样的架不住量多以及线程调度的工夫问题。

预渲染计划
理解RN的同学应该也晓得,js办法的执行和虚构dom的执行是这种框架的外围所在,想要撬动整个外围,基本上难度等同于重写一个了。那么剩下的计划也就只有一个:提前渲染。

[ 预渲染 ]

预渲染的计划批改了WeexFrameWork虚构dom和Android控件树转换的局部,在预渲染时,不生成真正的component和view构造,用形象进去的ComponentNode存储虚构dom的操作,并在RealRender的时候将node转换成一个个component以及View。

这个计划的基本原理就是典型的以提前生产的空间换取工夫,不去转换真正的component和View起因是view在不同context中的不可复用性以及view自身会占用大部分内存。

预渲染优化数据

内存耗费
提前渲染必然导致类内存的提前耗费,在huawei nove3上测试失去,预渲染游戏首页时的峰值内存会去到10M,倾城之下然而在最初预渲染实现后GC会开释这部分内存,最终常驻内存为0.3M。 真正渲染游戏首页的内存峰值会去到20M,最初的常驻内存为5.6M。

能够看到预渲染对常驻内存的耗费极少,然而因为虚构dom执行,导致峰值内存偏高,在某些内存敏感场景下,还是会有肯定危险。

页面关上耗时
实验室中游戏首页的失常加载数据为900ms(曾经预加载,无网络耗时),通过预渲染,页面关上仅须要150ms。
现网数据:

[ 预渲染页面关上上报 ]

最初,来两张优化前后的比照图:

[ 预渲染: ]

[ 非预渲染: ]

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理