全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-07-07_听说Compose与RecyclerView结合会有水土不服?

您的位置:首页 >> 新闻 >> 行业资讯

听说Compose与RecyclerView结合会有水土不服? 背景&笔者碎碎谈最近Compose也慢慢火起来了,作为google力推的ui框架,我们也要用起来才能进步呀!在最新一期的评测中LazyRow等LazyXXX列表组件已经慢慢逼近RecyclerView的性能了!但是还是有很多同学顾虑呀!没关系,我们就算用原有的view开发体系,也可以快速迁移到compose,这个利器就是ComposeView了,那么我们在RecyclerView的基础上,集成Compose用起来!这样我们有RecyclerView的性能又有Compose的好处不是嘛!相信很多人都有跟我一样的想法,但是这两者结合起来可是有隐藏的性能开销!(本次使用compose版本为1.1.1) 在原有view体系接入Compose在纯compose项目中,我们都会用setContent代替原有view体系的setContentView,比如 setContent{ ComposeTestTheme{ //Asurfacecontainerusingthe'background'colorfromthetheme Surface(modifier=Modifier.fillMaxSize(),color=MaterialTheme.colors.background){ Greeting("Android") Hello() } } } 那么setContent到底做了什么事情呢?我们看下源码 publicfunComponentActivity.setContent( parent:CompositionContext?=null, content:@Composable()-Unit ){ valexistingComposeView=window.decorView .findViewByIdViewGroup(android.R.id.content) .getChildAt(0)as?ComposeView if(existingComposeView!=null)with(existingComposeView){ setParentCompositionContext(parent) setContent(content) }elseComposeView(this).apply{ //第一步走到这里 //Setcontentandparent**before**setContentView //tohaveComposeViewcreatethecompositiononattach setParentCompositionContext(parent) setContent(content) //Settheviewtreeownersbeforesettingthecontentviewsothattheinflationprocess //andattachlistenerswillseethemalreadypresent setOwners() setContentView(this,DefaultActivityContentLayoutParams) } } 由于是第一次进入,那么一定就走到了else分支,其实就是创建了一个ComposeView,放在了android.R.id.content里面的第一个child中,这里就可以看到,compose并不是完全脱了原有的view体系,而是采用了移花接木的方式,把compose体系迁移了过来!ComposeView就是我们能用Compose的前提啦!所以在原有的view体系中,我们也可以通过ComposeView去“嫁接”到view体系中,我们举个例子 classCustomActivity:AppCompatActivity(){ overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_custom) valrecyclerView=this.findViewByIdRecyclerView(R.id.recyclerView) recyclerView.adapter=MyRecyclerViewAdapter() recyclerView.layoutManager=LinearLayoutManager(this) } } classMyRecyclerViewAdapter:RecyclerView.AdapterMyComposeViewHolder(){ overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):MyComposeViewHolder{ valview=ComposeView(parent.context) returnMyComposeViewHolder(view) } overridefunonBindViewHolder(holder:MyComposeViewHolder,position:Int){ holder.composeView.setContent{ Text(text="test$position",modifier=Modifier.size(200.dp).padding(20.dp),textAlign=TextAlign.Center) } } overridefungetItemCount():Int{ return200 } } classMyComposeViewHolder(valcomposeView:ComposeView):RecyclerView.ViewHolder(composeView){ } 这样一来,我们的compose就被移到了RecyclerView中,当然,每一列其实就是一个文本。嗯!普普通通,好像也没啥特别的对吧,假如这个时候你打开了profiler,当我们向下滑动的时候,会发现内存会慢慢的往上浮动 滑动嘛!有点内存很正常,毕竟谁不生成对象呢,但是这跟我们平常用RecyclerView的时候有点差异,因为RecyclerView滑动的涨幅可没有这个大,那究竟是什么原因导致的呢? 探究Compose有过对Compose了解的同学可能会知道,Compose的界面构成会有一个重组的过程,当然!本文就不展开聊重组了,因为这类文章有挺多的(填个坑,如果有机会就填),我们聊点特别的,那么什么时候停止重组呢?或者说什么时候这个Compose被dispose掉,即不再参与重组! Dispose策略其实我们的ComposeView,以1.1.1版本为例,其实创建的时候,也创建了取消重组策略,即 @Suppress("LeakingThis") privatevardisposeViewCompositionStrategy:(()-Unit)?= ViewCompositionStrategy.DisposeOnDetachedFromWindow.installFor(this) 这个策略是什么呢?我们点进去看源码 objectDisposeOnDetachedFromWindow:ViewCompositionStrategy{ overridefuninstallFor(view:AbstractComposeView):()-Unit{ vallistener=object:View.OnAttachStateChangeListener{ overridefunonViewAttachedToWindow(v:View){} overridefunonViewDetachedFromWindow(v:View?){ view.disposeComposition() } } view.addOnAttachStateChangeListener(listener) return{view.removeOnAttachStateChangeListener(listener)} } } 看起来是不是很简单呢,其实就加了一个监听,在onViewDetachedFromWindow的时候调用的view.disposeComposition(),声明当前的ComposeView不参与接下来的重组过程了,我们再继续看 fundisposeComposition(){ composition?.dispose() composition=null requestLayout() } 再看dispose方法 overridefundispose(){ synchronized(lock){ if(!disposed){ disposed=true composable={} valnonEmptySlotTable=slotTable.groupsSize0 if(nonEmptySlotTable||abandonSet.isNotEmpty()){ valmanager=RememberEventDispatcher(abandonSet) if(nonEmptySlotTable){ slotTable.write{writer- writer.removeCurrentGroup(manager) } applier.clear() manager.dispatchRememberObservers() } manager.dispatchAbandons() } composer.dispose() } } parent.unregisterComposition(this) } 那么怎么样才算是不参与接下里的重组呢,其实就是这里 slotTable.write{writer- writer.removeCurrentGroup(manager) } ... composer.dispose() 而removeCurrentGroup其实就是把当前的group移除了 for(slotingroupSlots()){ when(slot){ .... isRecomposeScopeImpl-{ valcomposition=slot.composition if(composition!=null){ composition.pendingInvalidScopes=true slot.composition=null } } } } 这里又多了一个概念,slottable,我们可以这么理解,这里面就是Compose的快照系统,其实就相当于对应着某个时刻view的状态!之所以Compose是声明式的,就是通过slottable里的slot去判断,如果最新的slot跟前一个slot不一致,就回调给监听者,实现更新!这里又是一个大话题了,我们点到为止 image.png跟RecyclerView有冲突吗我们看到,默认的策略是当view被移出当前的window就不参与重组了,嗯!这个在99%的场景都是有效的策略,因为你都看不到了,还重组干嘛对吧!但是这跟我们的RecyclerView有什么冲突吗?想想看!诶,RecyclerView最重要的是啥,Recycle呀,就是因为会重复利用holder,间接重复利用了view才显得高效不是嘛!那么问题就来了 如图,我们item5其实完全可以利用item1进行显示的对不对,差别就只是Text组件的文本不一致罢了,但是我们从上文的分析来看,这个ComposeView对应的composition被回收了,即不参与重组了,换句话来说,我们Adapter在onBindViewHolder的时候,岂不是用了一个没有compositon的ComposeView(即不能参加重组的ComposeView)?这样怎么行呢?我们来猜一下,那么这样的话,RecyclerView岂不是都要生成新的ComposeView(即每次都调用onCreateViewHolder)才能保证正确?emmm,很有道理,但是却不是的!如果我们把代码跑起来看的话,复用的时候依旧是会调用onBindViewHolder,这就是Compose的秘密了,那么这个秘密在哪呢 overridefunonBindViewHolder(holder:MyComposeViewHolder,position:Int){ holder.composeView.setContent{ Text(text="test$position",modifier=Modifier.size(200.dp).padding(20.dp),textAlign=TextAlign.Center) } } 其实就是在ComposeView的setContent方法中, funsetContent(content:@Composable()-Unit){ shouldCreateCompositionOnAttachedToWindow=true this.content.value=content if(isAttachedToWindow){ createComposition() } } funcreateComposition(){ check(parentContext!=null||isAttachedToWindow){ "createCompositionrequireseitheraparentreferenceortheViewtobeattached"+ "toawindow.AttachtheVieworcallsetParentCompositionReference." } ensureCompositionCreated() } 最终调用的是 privatefunensureCompositionCreated(){ if(composition==null){ try{ creatingComposition=true composition=setContent(resolveParentCompositionContext()){ Content() } }finally{ creatingComposition=false } } } 看到了吗!如果composition为null,就会重新创建一个!这样ComposeView就完全嫁接到RecyclerView中而不出现问题了! 其他Dispose策略我们看到,虽然在ComposeView在RecyclerView中能正常运行,但是还存在缺陷对不对,因为每次复用都要重新创建一个composition对象是不是!归根到底就是,我们默认的dispose策略不太适合这种拥有复用逻辑或者自己生命周期的组件使用,那么有其他策略适合RecyclerView吗?别急,其实是有的,比如DisposeOnViewTreeLifecycleDestroyed objectDisposeOnViewTreeLifecycleDestroyed:ViewCompositionStrategy{ overridefuninstallFor(view:AbstractComposeView):()-Unit{ if(view.isAttachedToWindow){ vallco=checkNotNull(ViewTreeLifecycleOwner.get(view)){ "Viewtreefor$viewhasnoViewTreeLifecycleOwner" } returninstallForLifecycle(view,lco.lifecycle) }else{ //Wechangethisreferenceafterwesuccessfullyattach vardisposer:()-Unit vallistener=object:View.OnAttachStateChangeListener{ overridefunonViewAttachedToWindow(v:View?){ vallco=checkNotNull(ViewTreeLifecycleOwner.get(view)){ "Viewtreefor$viewhasnoViewTreeLifecycleOwner" } disposer=installForLifecycle(view,lco.lifecycle) //Ensurethisrunsonlyonce view.removeOnAttachStateChangeListener(this) } overridefunonViewDetachedFromWindow(v:View?){} } view.addOnAttachStateChangeListener(listener) disposer={view.removeOnAttachStateChangeListener(listener)} return{disposer()} } } } 然后我们在ViewHolder的init方法中对composeview设置一下就可以了 classMyComposeViewHolder(valcomposeView:ComposeView):RecyclerView.ViewHolder(composeView){ init{ composeView.setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) } } 为什么DisposeOnViewTreeLifecycleDestroyed更加适合呢?我们可以看到在onViewAttachedToWindow中调用了installForLifecycle(view, lco.lifecycle) 方法,然后就removeOnAttachStateChangeListener,保证了该ComposeView创建的时候只会被调用一次,那么removeOnAttachStateChangeListener又做了什么呢? valobserver=LifecycleEventObserver{_,event- if(event==Lifecycle.Event.ON_DESTROY){ view.disposeComposition() } } lifecycle.addObserver(observer) return{lifecycle.removeObserver(observer)} 可以看到,是在对应的生命周期事件为ON_DESTROY(Lifecycle.Event跟activity生命周期不是一一对应的,要注意)的时候,才调用view.disposeComposition(),本例子的lifecycleOwner就是CustomActivity啦,这样就保证了只有当前被lifecycleOwner处于特定状态的时候,才会销毁,这样是不是就提高了compose的性能了! 扩展我们留意到了Compose其实存在这样的小问题,那么如果我们用了其他的组件类似RecyclerView这种的怎么办,又或者我们的开发没有读过这篇文章怎么办!(ps:看到这里的同学还不点赞点赞),没关系,官方也注意到了,并且在1.3.0-alpha02以上版本添加了更换了默认策略,我们来看一下 valDefault:ViewCompositionStrategy get()=DisposeOnDetachedFromWindowOrReleasedFromPool objectDisposeOnDetachedFromWindowOrReleasedFromPool:ViewCompositionStrategy{ overridefuninstallFor(view:AbstractComposeView):()-Unit{ vallistener=object:View.OnAttachStateChangeListener{ overridefunonViewAttachedToWindow(v:View){} overridefunonViewDetachedFromWindow(v:View){ //注意这里 if(!view.isWithinPoolingContainer){ view.disposeComposition() } } } view.addOnAttachStateChangeListener(listener) valpoolingContainerListener=PoolingContainerListener{view.disposeComposition()} view.addPoolingContainerListener(poolingContainerListener) return{ view.removeOnAttachStateChangeListener(listener) view.removePoolingContainerListener(poolingContainerListener) } } } DisposeOnDetachedFromWindow从变成了DisposeOnDetachedFromWindowOrReleasedFromPool,其实主要变化点就是一个view.isWithinPoolingContainer = false,才会进行dispose,isWithinPoolingContainer定义如下 image.png也就是说,如果我们view的祖先存在isPoolingContainer = true的时候,就不会进行dispose啦!所以说,如果我们的自定义view是这种情况,就一定要把isPoolingContainer变成true才不会有隐藏的性能开销噢!当然,RecyclerView也要同步到1.3.0-alpha02以上才会有这个属性改写!现在稳定版本还是会存在本文的隐藏性能开销,请注意噢!不过相信看完这篇文章,性能优化啥的,不存在了对不对! 结语Compose是个大话题,希望开发者都能够用上并深入下去,因为声明式ui会越来越流行,Compose相对于传统view体系也有大幅度的性能提升与架构提升!最后记得点赞关注呀!往期也很精彩! 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿[1]。 参考资料[1]https://juejin.cn/post/7112770927082864653: https://juejin.cn/post/7112770927082864653 阅读原文

上一篇:2022-04-02_最高每年40万港币奖学金,港大赵恒爽课题组CVMLAI方向博士博士后研究助理招募开启 下一篇:2022-09-02_苹果M1芯片上运行Stable Diffusion,生成图片只需15秒,几步搞定

TAG标签:

19
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价