探索淘宝购物车SurfaceView闪黑的解决方案

图片

如何应对产品形态与产品节奏相对确定情况下转变为『在业务需求与产品形态高度不确定性的情况下,如何实现业务交付时间与交付质量的确定性』。我们希望通过混合架构(Native 业务容器 + Weex 2.0)作为未来交易终端架构的重要演进方向,在 Native 容器侧充分发挥原生语言的性能优势、常驻 App 的调控与管控能力、手势识别与交互优势来解决体验问题。本专题《淘宝交易终端架构探索》是我们摸索出的部分实践总结,欢迎大家一起交流进步。

第一篇:《Weex购物车长列表横滑操作优化“编年史”》

第二篇:《淘宝页面首帧优化的经验和心得》

第三篇:《淘宝App交易链路终端混合场景体验探索》

第四篇:《淘宝订单列表

Fragment转场动画卡顿解决方案

第五篇:《探索淘宝购物车SurfaceView闪黑的解决方案》(本篇)

图片

引言

购物车的技术架构一直是DX+Native,在业务快速迭代的今天,可能不满足迭代效率,需要探索一种性能佳+迭代快的方式,由此开始探索购物车Weex化。

在进入主TabWeex购物车(SurfaceView)之后,我们发现切换到其他Tab(如:首页),再回到购物车,两次切换都能看到明显的闪黑。

图片

SurfaceView闪黑的原理是什么?

SurfaceView继承自 View 类,但与普通的 View 不同,它内部使用了一个独立的绘图表面(即 Surface),这个 Surface 可以在主 UI 线程之外的线程上进行控制和绘制。这样做的好处是,即使绘制操作很复杂,也不会影响到 UI 线程的响应性和流畅性。

View视图位于自绘视图Render Surface下方,通过对自绘视图的Render Surface挖洞来透出下面的View视图。

Tab切换页面的原理是FragmentManager的attach和detach,会触发Fragment根布局的add/remove,最终导致SurfaceView从View树上添加/移除,进而影响SurfaceView的可见性。

闪黑问题本质上是:SurfaceView的可见性发生变化时,会导致其内部的Surface被销毁/重建。在没有内容呈现的情况下,SurfaceView显示为黑色。

什么时候SurfaceView可见性会变化?

  •  SurfaceView被removeView,如:主Tab切换

  • Activity前后台切换,如:打开详情/回到手机Home

  • Activity销毁,如:finish页面


DX+Native购物车Tab切换为什么没有闪的问题呢?

DX采用原生View绘制,Fragment页面切换受VSync信号和MessageQueue的同步屏障控制,确保在执行完performTraversals()(即View的测量、布局和绘制三大流程)之后,才进行下一步操作,这样可以保证UI的一致性。

图片

方案

由于SurfaceView的特性,可见性变化的时候,就可能导致黑屏。业内有几种解决办法:

  1. 页面切换不执行remove SurfaceView,只执行visible/gone

  2. 盖View,遮住黑屏

  3.  Texture/Image->Surface切换

  Fragment不销毁

概述

主Tab页面切换,由FragmentManager的attach/detach改为show/hide,避免SurfaceView可见性变化


效果

Tab切换不会闪。

方案细节

修改Fragment切换方式:从attach/detach改为show/hide

show/hide不会触发购物车Fragment的生命周期回调,SurfaceView也不会从view树中移除,因此切换不会闪。

回到主Tab的场景,如果采用FragmentManagerattach/detach,会触发Fragment生命周期回调(onDestroyView),内部会把SurfaceView从view树上移除,从而触发onWindowVisibilityChanged,最终导致Surface被destroy,而调用show/hide并不会触发onWindowVisibilityChanged,所以不会导致Surface销毁。

  截屏

概述

从购物车切换到其他Tab时,保存购物车的截屏,再次从其他Tab进入购物车的时候,先显示截屏View,待SurfaceView开始渲染之后,再隐藏截屏View,显示SurfaceView。

效果

Tab切换不会闪,但截屏有API系统版本限制(Android 8.0,API 26)

方案细节
  1. 核心:SurfaceView从View树中移除,内部会销毁当次Surface,所以要想成功截屏必须在这个销毁动作执行之前。

  2. 截屏:在Tab切换Preload阶段/back键触发时截图,期间会卡主主线程(设置超时,如:100ms),在截屏完成之后(显示截屏兜底图),再切换Fragment

  • 购物车切到其他Tab,Nav Preload阶段截图并显示兜底图

// 切换到非购物车Tab回调
private val mTabOtherCallback: ()-> Unit = {
    screenShot()
}


// back键返回首页
override fun onBackPress(): Boolean {
    screenShot()
    return false
}


// 伪代码
private val screenShot() {
    val bitmap = requestBitmap()
    // 添加视图bitmap
    mScreenImageView?.setImageBitmap(bitMap)
    mScreenImageView?.setBackgroundColor(if (isSuccess) Color.TRANSPARENT else Color.WHITE)
    mScreenImageView?.visibility = View.VISIBLE
}
  • 回到购物车,weex视图开始绘制,移除兜底图

override fun onWeexUiDisplayed() {
    if (mScreenImageView?.visibility == View.VISIBLE) {
        mScreenImageView?.visibility = View.GONE
    }
}

‍3.  PixelCopy.request存在版本问题,只能在android 8.0及以上版本生效

api限制问题

由于PixelCopy.request截图存在系统版本限制,因此针对低于android8.0版本(如:Y67),做如下处理:

  1. 上述方案中购物车切换到其他Tab的时候,由显示截图改为显示白色背景图

  2. 显示和隐藏兜底图的时机不变

  3. 改动的实质是避免SurfaceView切换带来的闪黑问题,添加了白色背景图,从体感上没有那么的突兀

api限制另一种解决思路

通过Weex提供获取SurfaceView bitMap方式:

  1. 图片未采样,内存占用太高

  2. 图片应该用的是ARGB,购物车没有alpha透明度,可以直接用RGB,内存占用会更小一些

  3. 仍然存在耗时问题

  4. 列表滚动的时候,切换tab,需要先停止列表,然后再截图

影响

  1. 性能损耗:从购物车切换到其他Tab,会执行PixelCopy.request截图本身是耗时的,会卡主主线程,有一定的耗时

  2. 截图对内存有轻微影响,但对Java Heap影响不大

  3. 如果购物车列表处于滑动/动画/下拉/上拉等非静止状态,切换到其他Tab,再回到购物车,会导致截的图不是最后一帧,视觉上会导致页面抖动

  4. Android低系统版本体验有损,截图降级为白色背景图

  Image->Surface转换

概述

从购物车去到其他Tab,先切换成ImageView,同步渲染完,再跳转其他Tab;从其他Tab回到购物车,ImageView先上屏,再切换成SurfaceView。



效果

其他Tab进入购物车:iv->sv无白帧,很丝滑,购物车切到其他Tab:sv->iv有较多的白帧(待解决,参考了UC Hummer的方案)

方案细节

1.weex引擎提供ImageView->SurfaceView切换的能力

// instance renderMode设置为image
image->surface: 
    weex.promoteRenderSurface();
surface->image:
    weex.fallbackRenderSurface();

2.核心:

a.购物车去其他Tab的场景:

i.先切换成ImageView,同步渲染完,再跳转其他Tab

46ebed1335539794910c613414825ec2.png

b.其他Tab回到购物车场景:

i.首帧显示的是ImageView内容,切换到SurfaceView之前,需要保证SurfaceView已渲染的内容与ImageView一致,再切换到SurfaceView(解决ImageView切换成SurfaceView闪的问题

a469b388751e3c72c73d91570ab083fe.png

3.切换时机

a. 其他Tab进入购物车:

i.Weex首帧渲染时,调用promoteRenderSurface(),执行iv->sv切换

b. 购物车切换其他Tab:

i.Tab Preload中(非Cart场景),调用fallbackRenderSurface(),执行sv->iv切换



影响
  1. 进入购物车Tab会有一次iv->sv切换;退出购物车Tab会有一次sv->iv切换

  2. Tab切换,iv<->sv耗时:切换本身基本不会带来耗时,线下实测,Tab来回切换,也不会有卡顿

  3. 内存增长

测试结果

从测试的结果来看,iv<->sv切换,不会有耗时,也不会存在内存和CPU的明显增长,是一个性能比较不错的方案。

  Image->Surface转换+Fragment延迟销毁

效果

其他Tab进入购物车:iv->sv无白帧,很丝滑

购物车切到其他Tab:sv->iv无白帧,很丝滑

购物车滑动列表之后,切到其他Tab,不会闪,也不会跳帧

分析

image->Surface切换,解决了从其他Tab返回购物车黑屏/闪的问题,但是没有解决离开购物车,切到其他Tab闪的问题。

基于上述的结论,这里设想,从购物车切到其他Tab,先显示目标的Tab页面(如:首页),再销毁购物车,那不就解决了离开购物车闪的问题了吗?

本质上是延后了购物车的销毁,使得首页的内容盖在了购物车上面,因此再销毁购物车,就不会闪了

方案细节

1.核心代码(uikit_navigation):

// tab切换伪代码
if (tab != null) {
    if (tab.fragment != null) {
        final TabInfo tempLastTab = tab;


        mFragmentManager.registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks() {
            @Override
            public void onFragmentResumed(FragmentManager fm, Fragment f) {
                super.onFragmentResumed(fm, f);
                mFragmentManager.unregisterFragmentLifecycleCallbacks(this);


                f.getView().post(new Runnable() {
                    @Override
                    public void run() {
                        mFragmentManager.beginTransaction().detach(tempLastTab.fragment).commitAllowingStateLoss();
                    }
                });
            }
        }, false);
    }
}

这里调整了Tab切换的时候,上一个Fragment销毁的时机:下一个Fragment resume之后,并且该Fragment的root view post任务执行。

注:view的post任务执行,代表view已经执行了三大流程,绘制完页面了。

2.修改iv<->sv的切换时机

// 伪代码
override fun onPause() {
    // sv->iv
    weexInstance?.fallbackRenderSurface()
}


// 伪代码
override fun onResume() {
    // iv->sv
    weexInstance?.promoteRenderSurface()
}

切换时机放在fragment的生命周期里,onPause代表页面离开,onResume代表页面重新回到前台

  Texture->Surface转换

概述

从购物车Tab切换到其他Tab时,SurfaceView切换成TextureView,再次从其他Tab切换到购物车的时候,再把TextureView切换成SurfaceView



效果

从购物车切换到其他Tab,有概率会闪(取决于TextureView是否已经上屏),而从其他Tab再次进入购物车,大概率会闪(切换到SurfaceView会闪)

目前的效果不是很好,待与Weex团队一起优化


方案细节

1.weex引擎提供了内部转化能力:

weex.convertToSurfaceView
weex.convertToTextureView

2.切换时机:

a.从购物车切到其他Tab:Tab Preload阶段切换到TextureView

b.其他Tab进入购物车:weex渲染首帧,切换成SurfaceView

1d337dc53531fba0ecb9fdac26964a24.jpeg

总结

在不同的场景下,面对SurfaceView闪黑都有不同的解法。

结合具体的业务,以及对内存和性能的影响,去选择一个更合适的方案。



目前效果不错的方案有Fragment不销毁,截屏ImageView<->SurfaceView切换+Fragment延迟销毁两个方案

  • Fragment不销毁:性能很好,不存在耗时问题,但是视图都不销毁,存在一定的内存问题,并且对现有业务有一定的侵入性

  • 截屏:逻辑简单,但存在耗时性能问题,并且如果截图/隐藏截图的时序有问题,是有可能导致购物车无法操作的,存在一个潜在的风险点。

  • ImageView<->SurfaceView切换+Fragment延迟销毁:性能最佳,不存在不可操作的问题(ImageView方案本质也是自绘,可滑动,可点击)。但是要修改Fragment的销毁时机,对现有架构有一定的侵入性

图片

团队介绍

我们是淘天集团-基础交易终端团队,一支专注于手淘APP交易域(购物车、下单、订单、物流等)业务研发和体验优化的技术团队。在丰富的业务场景下,我们通过持续的技术探索、不断的创新突破,给数亿用户提供极致可靠的交易保障、极致流畅的操作交互以及极致顺滑的购物体验。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值