游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

听说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{AsurfacecontainerusingthebackgroundcolorfromthethemeSurface(modifierModifier。fillMaxSize(),colorMaterialTheme。colors。background){Greeting(Android)Hello()}}}
  那么setContent到底做了什么事情呢?我们看下源码publicfunComponentActivity。setContent(parent:CompositionContext?null,content:Composable()Unit){valexistingComposeViewwindow。decorView。findViewByIdViewGroup(android。R。id。content)。getChildAt(0)as?ComposeViewif(existingComposeView!null)with(existingComposeView){setParentCompositionContext(parent)setContent(content)}elseComposeView(this)。apply{第一步走到这里SetcontentandparentbeforesetContentViewtohaveComposeViewcreatethecompositiononattachsetParentCompositionContext(parent)setContent(content)SettheviewtreeownersbeforesettingthecontentviewsothattheinflationprocessandattachlistenerswillseethemalreadypresentsetOwners()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。activitycustom)valrecyclerViewthis。findViewByIdRecyclerView(R。id。recyclerView)recyclerView。adapterMyRecyclerViewAdapter()recyclerView。layoutManagerLinearLayoutManager(this)}}classMyRecyclerViewAdapter:RecyclerView。AdapterMyComposeViewHolder(){overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):MyComposeViewHolder{valviewComposeView(parent。context)returnMyComposeViewHolder(view)}overridefunonBindViewHolder(holder:MyComposeViewHolder,position:Int){holder。composeView。setContent{Text(texttestposition,modifierModifier。size(200。dp)。padding(20。dp),textAlignTextAlign。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{vallistenerobject:View。OnAttachStateChangeListener{overridefunonViewAttachedToWindow(v:View){}overridefunonViewDetachedFromWindow(v:View?){view。disposeComposition()}}view。addOnAttachStateChangeListener(listener)return{view。removeOnAttachStateChangeListener(listener)}}}
  看起来是不是很简单呢,其实就加了一个监听,在onViewDetachedFromWindow的时候调用的view。disposeComposition(),声明当前的ComposeView不参与接下来的重组过程了,我们再继续看fundisposeComposition(){composition?。dispose()compositionnullrequestLayout()}
  再看dispose方法overridefundispose(){synchronized(lock){if(!disposed){disposedtruecomposable{}valnonEmptySlotTableslotTable。groupsSize0if(nonEmptySlotTableabandonSet。isNotEmpty()){valmanagerRememberEventDispatcher(abandonSet)if(nonEmptySlotTable){slotTable。write{writerwriter。removeCurrentGroup(manager)}applier。clear()manager。dispatchRememberObservers()}manager。dispatchAbandons()}composer。dispose()}}parent。unregisterComposition(this)}
  那么怎么样才算是不参与接下里的重组呢,其实就是这里slotTable。write{writerwriter。removeCurrentGroup(manager)}。。。composer。dispose()
  而removeCurrentGroup其实就是把当前的group移除了for(slotingroupSlots()){when(slot){。。。。isRecomposeScopeImpl{valcompositionslot。compositionif(composition!null){composition。pendingInvalidScopestrueslot。compositionnull}}}}
  这里又多了一个概念,slottable,我们可以这么理解,这里面就是Compose的快照系统,其实就相当于对应着某个时刻view的状态!之所以Compose是声明式的,就是通过slottable里的slot去判断,如果最新的slot跟前一个slot不一致,就回调给监听者,实现更新!这里又是一个大话题了,我们点到为止。
  跟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(texttestposition,modifierModifier。size(200。dp)。padding(20。dp),textAlignTextAlign。Center)}}
  其实就是在ComposeView的setContent方法中,funsetContent(content:Composable()Unit){shouldCreateCompositionOnAttachedToWindowtruethis。content。valuecontentif(isAttachedToWindow){createComposition()}}funcreateComposition(){check(parentContext!nullisAttachedToWindow){createCompositionrequireseitheraparentreferenceortheViewtobeattachedtoawindow。AttachtheVieworcallsetParentCompositionReference。}ensureCompositionCreated()}
  最终调用的是privatefunensureCompositionCreated(){if(compositionnull){try{creatingCompositiontruecompositionsetContent(resolveParentCompositionContext()){Content()}}finally{creatingCompositionfalse}}}
  看到了吗!如果composition为null,就会重新创建一个!这样ComposeView就完全嫁接到RecyclerView中而不出现问题了!其他Dispose策略
  我们看到,虽然在ComposeView在RecyclerView中能正常运行,但是还存在缺陷对不对,因为每次复用都要重新创建一个composition对象是不是!归根到底就是,我们默认的dispose策略不太适合这种拥有复用逻辑或者自己生命周期的组件使用,那么有其他策略适合RecyclerView吗?别急,其实是有的,比如DisposeOnViewTreeLifecycleDestroyedobjectDisposeOnViewTreeLifecycleDestroyed:ViewCompositionStrategy{overridefuninstallFor(view:AbstractComposeView):()Unit{if(view。isAttachedToWindow){vallcocheckNotNull(ViewTreeLifecycleOwner。get(view)){ViewtreeforviewhasnoViewTreeLifecycleOwner}returninstallForLifecycle(view,lco。lifecycle)}else{Wechangethisreferenceafterwesuccessfullyattachvardisposer:()Unitvallistenerobject:View。OnAttachStateChangeListener{overridefunonViewAttachedToWindow(v:View?){vallcocheckNotNull(ViewTreeLifecycleOwner。get(view)){ViewtreeforviewhasnoViewTreeLifecycleOwner}disposerinstallForLifecycle(view,lco。lifecycle)Ensurethisrunsonlyonceview。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又做了什么呢?valobserverLifecycleEventObserver{,eventif(eventLifecycle。Event。ONDESTROY){view。disposeComposition()}}lifecycle。addObserver(observer)return{lifecycle。removeObserver(observer)}
  可以看到,是在对应的生命周期事件为ONDESTROY(Lifecycle。Event跟activity生命周期不是一一对应的,要注意)的时候,才调用view。disposeComposition(),本例子的lifecycleOwner就是CustomActivity啦,这样就保证了只有当前被lifecycleOwner处于特定状态的时候,才会销毁,这样是不是就提高了compose的性能了!扩展
  我们留意到了Compose其实存在这样的小问题,那么如果我们用了其他的组件类似RecyclerView这种的怎么办,又或者我们的开发没有读过这篇文章怎么办!(ps:看到这里的同学还不点赞点赞),没关系,官方也注意到了,并且在1。3。0alpha02以上版本添加了更换了默认策略,我们来看一下valDefault:ViewCompositionStrategyget()DisposeOnDetachedFromWindowOrReleasedFromPoolobjectDisposeOnDetachedFromWindowOrReleasedFromPool:ViewCompositionStrategy{overridefuninstallFor(view:AbstractComposeView):()Unit{vallistenerobject:View。OnAttachStateChangeListener{overridefunonViewAttachedToWindow(v:View){}overridefunonViewDetachedFromWindow(v:View){注意这里if(!view。isWithinPoolingContainer){view。disposeComposition()}}}view。addOnAttachStateChangeListener(listener)valpoolingContainerListenerPoolingContainerListener{view。disposeComposition()}view。addPoolingContainerListener(poolingContainerListener)return{view。removeOnAttachStateChangeListener(listener)view。removePoolingContainerListener(poolingContainerListener)}}}
  DisposeOnDetachedFromWindow从变成了DisposeOnDetachedFromWindowOrReleasedFromPool,其实主要变化点就是一个view。isWithinPoolingContainerfalse,才会进行dispose,isWithinPoolingContainer定义如下:
  也就是说,如果我们view的祖先存在isPoolingContainertrue的时候,就不会进行dispose啦!所以说,如果我们的自定义view是这种情况,就一定要把isPoolingContainer变成true才不会有隐藏的性能开销噢!当然,RecyclerView也要同步到1。3。0alpha02以上才会有这个属性改写!现在稳定版本还是会存在本文的隐藏性能开销,请注意噢!不过相信看完这篇文章,性能优化啥的,不存在了对不对!结语
  Compose是个大话题,希望开发者都能够用上并深入下去,因为声明式ui会越来越流行,Compose相对于传统view体系也有大幅度的性能提升与架构提升!最后记得点赞关注呀!往期也很精彩!
  作者:Pika
  链接:https:juejin。cnpost7117176526893744142

三思三思(一)我从过去所学到的教训就是:不要让任何人使你变得无情。无论日子多么艰难,失去自我那是最大的代价。(二)始终考虑别人的感受,在你评判一个人之前,你……联想拯救者刃7000K9000K2021台式机发布RTX30IT之家4月18日消息联想于今日下午正式发布刃7000K9000K2021款台式机。这两款产品均搭载英特尔11代酷睿处理器,配备英伟达RTX3060Ti、RTX3070、RTX……华硕TUFDashF15笔记本上架亚马逊,搭载RTX3050IT之家4月19日消息华硕TUFDashF15笔记本此前在印度地区发布。此款笔记本搭载15。6英寸IPS屏,配备i711370H处理器,显卡采用的是RTX3060,售价约1。2……CPUZ已初步支持英特尔第12代酷睿处理器IT之家4月16日消息电脑硬件监控软件CPUZ已获得1。96版本更新,添加了对英特尔第12代AlderLake处理器和Z6XX主板的初步支持。官方表示,第12代Alder……卧龙苍天陨落新手操作指南卧龙苍天陨落战斗技巧卧龙苍天陨落正式发售,本作的战斗系统士气是决定敌我战斗力的关键因素,不断的击败对手来提升自身的士气,活用化解与防御能够击败拥有强大杀招的敌人。卧龙苍天陨落新手操作指南卧龙苍天陨……5066元,联想小新Pro142021酷睿标压版开启预售16IT之家3月24日消息联想刚刚公布了联想小新Pro142021的价格:标压锐龙版(R75800H)5199元、酷睿标压版(i511300H)4999元。酷睿标压版现已开启预售,……中国牵头!首个自动驾驶国际标准出台,相关人才抢夺战打响!近日,工业和信息化部正式发布由我国牵头制定的首个自动驾驶国际标准。这一国际标准是我国组织德国等二十多个国家的专家,共同对自动驾驶测试场景标准体系做了系统规划。其正式出台,为自动……小新Pro142021价格公布标压锐龙5199元酷睿4999IT之家3月24日消息联想今日公布了联想小新Pro142021的价格:标压锐龙版(R75800H)5199元、酷睿标压版(i511300H)4999元。IT之家了解到,小……联想YOGA14s标压锐龙版配置公布R75800HVega8IT之家3月24日消息联想YOGA14s2021标压锐龙版将于3月31日开启预售,现在联想官方公布了这款笔记本的详细配置。IT之家了解到,联想YOGA14s2021标压锐……549元,戴尔推出MS7421W无线鼠标仅重80克,充电两分IT之家3月21日消息戴尔在此前的CES大会上展示了一款鼠标MS7421W,主打超长续航和多设备链接功能,本周五终于上架,目前仅有铂金银配色,官方商城售价549元,似乎暂未在京……戴尔游匣G15进阶无双版发布搭载RTX30系显卡,最高360IT之家3月9日消息戴尔前几日推出了新款的游匣G15笔记本,该笔记本搭载了i510200H处理器和GTX1650显卡,首发售价5599元。今日,戴尔宣布将推出游匣G15的……戴尔XPS132021款上市采用老款模具,7999元起IT之家2月26日消息本月初,戴尔推出了XPS132021款,型号为9305,采用了老款XPS13的设计语言,但价格更低,i51135G716GB内存512GBSSD售价799……
一味追求销量,却对售后不顾?苹果和小米形成鲜明对比近几年来,苹果的口碑下滑有多厉害我们也都看在了眼里,不单单是因为创新方面的不足,其实更多的还是因为苹果的各种著名门事件所败坏了口碑。其实这些事件大多都反应了一个点,那就是售后服……我的十八岁不是天堂我的十八岁不是天堂在广场排队等奶茶,奶茶店里放着一首很好听的歌,路上无聊所以我便入了迷。打扮漂亮十八岁是天堂呵,我自嘲一笑,我的十八岁不是天堂。准确来说,整个……充满遗憾的故事文案1。很抱歉,终究没能成为你的例外。2。从前有个人爱你很久,但偏偏风渐渐,把距离吹得好远。3。我们好像不该这样,不止这样,但也只能这样。4。后来啊,书没有读好,……162cm的吴昕现实中长啥样?线下被拍生图,状态令人感慨《快乐大本营》作为一档王牌节目,从1997年创办开始已风雨兼程走过20多个年头。在它的巅峰时期,快乐家族成员人气几乎可以比肩一线明星,主持人之一的谢娜粉丝量更是突破1亿。……完全体P50官网低调上线,搭载骁龙888支持5G去年7月份,华为P50终于姗姗来迟,算是给期待已久的花粉们一个交代。庆幸的是,华为还能推出新品旗舰来维持手机的业务,难过的是,华为P50系列全系均不支持5G。以至于作为华为终端……小米civi2曝光,屏幕有所升级,但是性能表现可能依旧一般作为小米的女性手机市场产品,小米civi这款手机现在在市场上的表现情况的确可以说是非常的好的,有着很不错的一个市场发展表现情况体现,尤其是现在在上一代已经发展了这么久手机市场之……CBA孙悦复出为挣钱?职业生涯足足挣半个亿,退役就惨遭娇妻抛近日有很多媒体关注到CBA的最新比赛情况,目前CBA的季后赛正在如火如荼的开展着,联盟的几支优秀球队都来到了最终的决赛阶段,辽宁男篮和广东男篮的半决赛吸引了很多的球迷,究竟谁能……包头龙白垩纪的植食巨兽包头龙属(学名:Bactrosaurus)是一类已灭绝的角龙类恐龙,生活于约1。3亿年前的晚白垩世。其化石发现于中国内蒙古自治区的伊金霍洛旗地区。该恐龙的体长可达58米,属于中……91家独角兽总部云集北京,包括最大的字节跳动全球独角兽企业,都有谁?它们分布在哪里?最新答案来了!12月20日,胡润研究院携手广州市商务局、广州市黄埔区联合发布《2021全球独角兽榜》(GlobalUnicornI……2022轻薄本年度盘点性能飞跃,设计疯狂进化近几年轻薄本的发展极为迅猛,每一年都在成长,都有创新。而2022年的轻薄本绝对是近几年里最具颠覆性的一代产品!这一年,轻薄本的核心硬件规格再次得到了飞跃式提升,无论是处理器还是……天空体育评英超战力值孙兴慜第一力压萨拉赫丁丁第六B费第七北京时间5月10日消息,近日,英国天空体育官方发布了本赛季英超球员战力排行榜,孙兴慜在这份榜单中力压萨拉赫,位居榜首。孙兴慜vs萨拉赫数据比较:进球:2022……并肩同行!武汉队官宣与李金羽续约,新赛季继续率队征战中超北京时间2月25日,中超球队武汉队官方宣布,与李金羽完成续约,新赛季中继续担任武汉队主教练。上赛季,李霄鹏成为国足主帅后,李金羽接替李霄鹏成为武汉队主帅,并且保级成功。附……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网