跨越适配&性能那道坎,企鹅电竞Android weex优化
Posted 腾讯WeTest
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跨越适配&性能那道坎,企鹅电竞Android weex优化相关的知识,希望对你有一定的参考价值。
WeTest 导读
企鹅电竞从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的复用流程:
[ RecyclerView复用 ]
weex中的RecyclerView并没有设置stableId,所以RecyclerView的所有复用都依赖于ViewHolder的ViewType,Weex的ViewType生成见下图:
private int generateViewType(WXComponent component) {
long id;
try {
id = Integer.parseInt(component.getRef());
String type = component.getAttrs().getScope();
if (!TextUtils.isEmpty(type)) {
if (mRefToViewType == null) {
mRefToViewType = new ArrayMap<>();
}
if (!mRefToViewType.containsKey(type)) {
mRefToViewType.put(type, id);
}
id = mRefToViewType.get(type);
}
} catch (RuntimeException e) {
WXLogUtils.eTag(TAG, e);
id = RecyclerView.NO_ID;
WXLogUtils.e(TAG, "getItemViewType: NO ID, this will crash the whole render system of WXListRecyclerView");
}
return (int) id;
}
在没有设置scope的情况下,viewHolder的component的ref就是viewType,即所有的ViewHolder都是不同且不可复用的,此时的RecyclerView也就退化成了一个稍微复杂一点的ScrollView。
如果设置了scope属性,但你绝对想不到,scope本身也是一个坑。下面直接上代码:
// BasicListComponent.onBindViewHolder()
public void onBindViewHolder(final ListBaseViewHolder holder, int position) {
...
if (holder.getComponent() != null && holder.getComponent() instanceof WXCell) {
if(holder.isRecycled()) {
holder.bindData(component);
component.onRenderFinish(STATE_UI_FINISH);
}
...
}
}
// ListBaseViewHolder.bindData()
public void bindData(WXComponent component) {
if (mComponent != null && mComponent.get() != null) {
mComponent.get().bindData(component);
isRecycled = false;
}
}
上面代码中,可以看到,使用了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各自的圆角方案说起。
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,并不会导致内存有过多的问题。
下面贴一下后处理器处理圆角的关键代码:
public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {
CloseableReference<Bitmap> bitmapRef = null;
try {
if (mInnerImageView instanceof FrescoImageView && sourceBitmap != null && !sourceBitmap.isRecycled()
&& sourceBitmap.getWidth() > 0 && sourceBitmap.getHeight() > 0) {
...
// 解决Bitmap绘制尺寸上限问题,比如:Bitmap too large to be uploaded into a texture (1302x9325, max=8192x8192)
int maxSize = EGLUtil.getGLESTextureLimit();
int resizeWidth = mWidth;
int resizeHeight = mHeight;
float ratio = 0;
if (maxSize > 0 && (mWidth > maxSize || mHeight > maxSize)) {
 以上是关于跨越适配&性能那道坎,企鹅电竞Android weex优化的主要内容,如果未能解决你的问题,请参考以下文章