Android 业务逻辑应该如何写(第二篇)(本文)
View的组织形式
再次重复一次:何为view的组织形式呢,简单点解释也就是用一种什么样的方法将各个view组合成一个完整的页面,当然把所有的view都堆积在一个 xml里面,也是一种方法,只不过这样的话你的xml会超过几千行哦
先抛出一个问题,如下图为抖音的推荐流, 如果你是抖音的开发者,你将如何编写该页面的View
我妄自揣测一下:
A 同学:这个页面不就几张图片:"头像"、"点赞"、"评论"、"收藏"、"不喜欢"、"分享"、"音乐转盘",
以及几个文案:"用户名"、"视频简介"、"相关搜索信息",然后再加上一个广告的图片和相关文案。
这几个view通过各种布局摆放在一个xml中,然后通过ViewModel进行逻辑处理,Livedata通知ui更新。
对于评论弹窗我直接单独一个Fragment就解决了。
B 同学: 我可以把 "头像"、"点赞"、"评论"、"收藏"、"不喜欢"、"分享"、"音乐转盘"等信息用一个自定义view给封装起来,在主xml中写入自定义view和其他view元素,这样可以将一些复杂的view拆分一点。然后再通过MVVM或者MVP等混合形式来进行数据通信和view的更新。
对于评论弹窗可以用BottomSheetDialog或者BottomSheetDialogFragment。
其实没有什么标准或者正确答案,但一个健壮、高效率、易扩展的View框架的基本思想都大同小异。
先分享其他两个大厂他们的View业务框架是怎么样的
小红书
time.geekbang.org/qconplus/de...
极客时间上小红书的分享,可能需要开通超级会员才可以看(我这里不是在推销大家买会员哦~)
微博
微博相关的分享是很早了,2018年,本来是一个完整的ppt,但是掘金这里无法插入ppt附件,我把网盘的链接放到这里,感兴趣的同学可以下载看看。 【超级会员V7】通过百度网盘分享的文件:议题十四 微博信... 链接:pan.baidu.com/s/1Qhv-p3KA... 提取码:7w7z】
在正文我将与本文主题相关的ppt页面粘贴在下方,方便大家查看
回到原问题
看完上面两个大厂的案例,我们每个人可能都会有自己的思考,或清晰、或模糊、但可能也会疑问:
一个页面的逻辑编写用得着这么复杂吗!
是的
一个巨量app他就是这么复杂。在一个页面中,包含着你看的见元素,也包括看不见的 埋点 、实验、以及还未被触发业务。同时一个页面中的所有元素,不见得属于一个模块、更甚至开发的同学都不是一个组,都没见过面(公司内网友)。这些不同模块间View的通信,不同开发同学的代码风格(亦或者技术水平的参差)等都会导致代码bug过多、开发效率低下,以及后续想要对某个业务优化或者调整都将会有很大的挑战。
所以才需要一个"精美"的View框架去让业务代码变的更加"轻快"。
写到这里感觉要即将推出一款新的开源框架似的,不过本文还是注重其思想吧(大家也可以理解成嘴炮)
直接先推荐一个现成的开源框架吧【github.com/kaleai/Shat...
这个不是我写的~是找了一个思想差不多,并且很优秀的一个开源框架【kaleai/Shatter】,有兴趣的可以体会下
再重新回到本节的首个问题。
B 同学的思路可能稍微先进了一些,他有意识的将复杂view进行拆分,然后再组合。
拆分和组合 是我想要着重表达的。
拆分和组合 也是一个好的View框架的基础。
在这个基础之上,可能我们还需要考虑以下几点:
-
View之间的通信问题,包括业务状态以及数据的通信
-
View对Activity或Fragment生命周期的感知
-
View的复用、异步加载、预加载、懒加载等有关性能的问题
View的 拆分和组合
还是以抖音的推荐页面为例,我们将对此进行拆分
和【github.com/kaleai/Shat... 框架一样,我们也有一个LightFragment。该LightFragment拥有和原生Fragment几乎一样的能力
- 包括它(LightFragment)是一个view的承载容器
- 它拥有和Fragment一样的生命周期
- 同样的它也有业务逻辑(你也可以使用mvvm等框架,将业务逻辑写到Viewmodel中,但又因为该LightFragment 中的业务足够单纯和轻量,也可以写成mvc模式)
同时该LightFragment也有原声Fragment没有的能力
- 它底层打通了所有LightFragment之间的通信(这里好像小米的新系统-Xiaomi HyperOS中提到的万物互联)
- 它还实现了对View加载优先级的定义,可以满足view的预加载、懒加载、甚至在子线程(子线程只是不能更新ui,加载一个view还是可以的),跨页面的加载
同时 LightFragment 又像是ViewGroup和View一样,可以像 ViewGroup.addView
一样,添加子View
这就是我们所定义的 LightFragment
它可以将业务分为无数个LightFragment,同时也可以通过addLightFragment,将各个LightFragment组合成不同的页面。
就像上图所示,我们将可以把整个页面分为【上】【下】【左】【右】【中】五大区域,比如对于【右】区域,我们命名 RightRegionFragment, 然后 RightRegionFragment 又包括 【 头像 Fragment】、【喜欢Fragment】、【评论Fragment】等等。再比如播放器View【VideoFragment】应该在view层级的最底层,我们可以通过 LightFragmentManager. addLightFragment
将页所有的LightFragment组合起来,其中父LightFragment再通过 addLightFragment
把子LightFragment传起来。
这就是View的拆分和组合。
View之间的通信问题,包括业务状态以及数据的通信
我们将所有的业务都细分拆到了各个 LightFragment 中 , 但是实际业务开发中 ,各个 LightFragment并不是独立的 。我们依然拿下面两个图的例子来说明。
第一张图的左侧有【用户名字 (UsernNameFragment )】和【视频简介(VideoIntroductionFragment )】两个信息。第二种图【UsernNameFragment 】和【VideoIntroductionFragment 】没有了,【广告卡片(adCradFragment)】信息出来了。
那么这三个LightFragment(UsernNameFragment、VideoIntroductionFragment、adCradFragment) 就必须相互知道各自的状态。当adCradFragment 要出来的时候,就要通知其他两个LightFragment消失,反之亦然。
问题来了,要通过什么样的方式进行各个LightFragment通信呢?
- 通过全局的ViewModel和LiveData ?
这确实是一种方式,不过还是觉得有点重,如果业务通信很多,那么要重复写很多LiveData吧,并且LiveData只是一个UI数据保存的工具,切不可当作事件通知工具,这点相信会有很多同学误解
最重要的是:如果用着用形式,我们应该把ViewModel和LiveData写到哪里呢?要知道实际业务开发中LightFragment 可能存在于各个不同的模块中 , 甚至如果app是基于内插件,那么LightFragment也可能会分散到各个插件中,所以想要在各个地方拿到同一个自定义的ViewModel或LiveData似乎是一个很复杂的事情。不可取!
-
通过EventBus?
EventBus我本身就不会推荐去使用,我甚至认为经常使用EventBus的同学是一种偷懒的行为,还有类似发明的LiveDataBus,这些通信工具太过于强大,过于解耦,太多了会导致不可控。
-
通过的接口、单例也不太行,缺点太明显了
-
最后选择了Rxjava
实现思路我们把所有的数据或者事件都先发送给大总管LightFragmentManager, 在通过LightFragmentManager 中转给各个子LightFragment
所以,如果想要 发送/获取 某个状态或数据,就可以通过LightFragmentManager来保存和分发
View对Activity或Fragment生命周期的感知
大家应该都LeakCanary吧, 它是如何监控Activity以及Fragment的生命周期的,我们的LightFragment就是如何监控的,不同的是实际上是LightFragmentManager 来进行监控的,然后再将获取到的各个状态分发给其他子LightFragment。
LightFragmentManager 很重要 , 就是用来保存并主动分发(Rxjava)数据和状态的。同时也是创建View树的起点
View的复用、异步加载、预加载、懒加载等有关性能的问题
这个话题其实有点大
View的复用
这里具体来说是LightFragment的复用
说到view的复用,可能最先想到的是RecycleView,RecycleView的最核心的能力就是对view的复用能力,当然也包括view所对应的业务逻辑代码的复用。
- 同样的,除来在RecycleView中。我们的其他业务也可能会用到,比如在A页面有用户名字的展示【UsernNameFragment 】,在B页面也有【用户名字】的展示,我们就可以直接拿到现成的UsernNameFragment就添加到B页面就可以了,有关【用户名字】的点击、展示逻辑,甚至埋点都不用写。
- 再比如大型app会有很多实验,对不同的元素在不同的实验分组下有不同的ui或逻辑,我们就可以直接写两个相关的LightFragment,避免在一个类中写很多if-else来区分实验逻辑。
- 再比如有些业务需要回退、下线。我们如果直接revert代码的话,可能会造成很多冲突,解冲突又可能会出现bug,耗时费力不讨好。如果选择手动删除代码的话,也会有类似问题。如果有了LightFragment,我们就可以精准的找到需要删除、下线的LightFragment,几乎不用怎么测试。
当然上面所提到了例子,有同学可能认为这不是view的复用,嗯呢写到这里我也觉得起名view的复用似乎不太合适,就先这样吧。
异步加载
这里指LightFragment中view在子线程中加载
关于view在子线程中加载话题会更大,这里就暂时不展开了,后续会单独开一小节来讲述。
预加载、懒加载
写到这里已经深夜2点12分了,脑子开始偷懒了,想要快速结束本节,就把预加载、懒加载一起写了吧
在业务中,有些view的展示优先级比较高,就拿抖音feed流来说,用户信息等view等元素一定是最重要的,这些元素不能懒加载,甚至为了视频滑动更流畅、高优先级的view展示的更快,我们对各个view(LightFragment )做了优先级,高优先级的LightFragment 会提前或正常加载。
在比如有些广告转化卡片,它的出现是有时机的,比如需要时间播放到第5秒时,弹出广告转化卡片,那么对于此类LightFragment我们不必着急的去加载它,也就是懒加载,可以在程序运行中动态加载各个LightFragment。