如何应对产品形态与产品节奏相对确定情况下转变为『在业务需求与产品形态高度不确定性的情况下,如何实现业务交付时间与交付质量的确定性』。我们希望通过混合架构(Native 业务容器 + Weex 2.0)作为未来交易终端架构的重要演进方向,在 Native 容器侧充分发挥原生语言的性能优势、常驻 App 的调控与管控能力、手势识别与交互优势来解决体验问题。本专题《淘宝交易终端架构探索》是我们摸索出的部分实践总结,欢迎大家一起交流进步。
第一篇:《Weex购物车长列表横滑操作优化“编年史”》
第二篇:《淘宝页面首帧优化的经验和心得》
第三篇:《淘宝App交易链路终端混合场景体验探索》
第四篇:《淘宝订单列表
Fragment转场动画卡顿解决方案
》
第五篇:《探索淘宝购物车SurfaceView闪黑的解决方案》(本篇)
引言
购物车的技术架构一直是DX+Native,在业务快速迭代的今天,可能不满足迭代效率,需要探索一种性能佳+迭代快的方式,由此开始探索购物车Weex化。
在进入主TabWeex购物车(SurfaceView)之后,我们发现切换到其他Tab(如:首页),再回到购物车,两次切换都能看到明显的闪黑。
,时长00:09
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的特性,可见性变化的时候,就可能导致黑屏。业内有几种解决办法:
- 页面切换不执行remove SurfaceView,只执行visible/gone
- 盖View,遮住黑屏
- Texture/Image->Surface切换
Fragment不销毁
概述
主Tab页面切换,由FragmentManager的attach/detach改为show/hide,避免SurfaceView可见性变化
效果
Tab切换不会闪。
,时长00:07
方案细节
修改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)
,时长00:07
方案细节
- 核心:SurfaceView从View树中移除,内部会销毁当次Surface,所以要想成功截屏必须在这个销毁动作执行之前。
- 截屏:在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),做如下处理:
- 上述方案中购物车切换到其他Tab的时候,由显示截图改为显示白色背景图
- 显示和隐藏兜底图的时机不变
- 改动的实质是避免SurfaceView切换带来的闪黑问题,添加了白色背景图,从体感上没有那么的突兀
api限制另一种解决思路
通过Weex提供获取SurfaceView bitMap方式:
- 图片未采样,内存占用太高
- 图片应该用的是ARGB,购物车没有alpha透明度,可以直接用RGB,内存占用会更小一些
- 仍然存在耗时问题
- 列表滚动的时候,切换tab,需要先停止列表,然后再截图
影响
- 性能损耗:从购物车切换到其他Tab,会执行PixelCopy.request截图本身是耗时的,会卡主主线程,有一定的耗时
- 截图对内存有轻微影响,但对Java Heap影响不大
- 如果购物车列表处于滑动/动画/下拉/上拉等非静止状态,切换到其他Tab,再回到购物车,会导致截的图不是最后一帧,视觉上会导致页面抖动
- Android低系统版本体验有损,截图降级为白色背景图
Image->Surface转换
概述
从购物车去到其他Tab,先切换成ImageView,同步渲染完,再跳转其他Tab;从其他Tab回到购物车,ImageView先上屏,再切换成SurfaceView。
效果
其他Tab进入购物车:iv->sv无白帧,很丝滑,购物车切到其他Tab:sv->iv有较多的白帧(待解决,参考了UC Hummer的方案)
,时长00:09
方案细节
1.weex引擎提供ImageView->SurfaceView切换的能力
// instance renderMode设置为image
image->surface:
weex.promoteRenderSurface();
surface->image:
weex.fallbackRenderSurface();
2.核心:
a.购物车去其他Tab的场景:
i.先切换成ImageView,同步渲染完,再跳转其他Tab
b.其他Tab回到购物车场景:
i.首帧显示的是ImageView内容,切换到SurfaceView之前,需要保证SurfaceView已渲染的内容与ImageView一致,再切换到SurfaceView(解决ImageView切换成SurfaceView闪的问题)
3.切换时机
a. 其他Tab进入购物车:
i.Weex首帧渲染时,调用promoteRenderSurface(),执行iv->sv切换
b. 购物车切换其他Tab:
i.Tab Preload中(非Cart场景),调用fallbackRenderSurface(),执行sv->iv切换
影响
- 进入购物车Tab会有一次iv->sv切换;退出购物车Tab会有一次sv->iv切换
- Tab切换,iv<->sv耗时:切换本身基本不会带来耗时,线下实测,Tab来回切换,也不会有卡顿
- 内存增长
测试结果
从测试的结果来看,iv<->sv切换,不会有耗时,也不会存在内存和CPU的明显增长,是一个性能比较不错的方案。
Image->Surface转换+Fragment延迟销毁
效果
其他Tab进入购物车:iv->sv无白帧,很丝滑
购物车切到其他Tab:sv->iv无白帧,很丝滑
购物车滑动列表之后,切到其他Tab,不会闪,也不会跳帧
,时长00:12
分析
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会闪)
,时长00:07
目前的效果不是很好,待与Weex团队一起优化
方案细节
1.weex引擎提供了内部转化能力:
weex.convertToSurfaceView
weex.convertToTextureView
2.切换时机:
a.从购物车切到其他Tab:Tab Preload阶段切换到TextureView
b.其他Tab进入购物车:weex渲染首帧,切换成SurfaceView
总结
在不同的场景下,面对SurfaceView闪黑都有不同的解法。
结合具体的业务,以及对内存和性能的影响,去选择一个更合适的方案。
目前效果不错的方案有Fragment不销毁,截屏和ImageView<->SurfaceView切换+Fragment延迟销毁两个方案
- Fragment不销毁:性能很好,不存在耗时问题,但是视图都不销毁,存在一定的内存问题,并且对现有业务有一定的侵入性
- 截屏:逻辑简单,但存在耗时性能问题,并且如果截图/隐藏截图的时序有问题,是有可能导致购物车无法操作的,存在一个潜在的风险点。
- ImageView<->SurfaceView切换+Fragment延迟销毁:性能最佳,不存在不可操作的问题(ImageView方案本质也是自绘,可滑动,可点击)。但是要修改Fragment的销毁时机,对现有架构有一定的侵入性
团队介绍
我们是淘天集团-基础交易终端团队,一支专注于手淘APP交易域(购物车、下单、订单、物流等)业务研发和体验优化的技术团队。在丰富的业务场景下,我们通过持续的技术探索、不断的创新突破,给数亿用户提供极致可靠的交易保障、极致流畅的操作交互以及极致顺滑的购物体验。