Android 复杂业务碎片化管理方案

前言

之前看了一个大佬写的文章 《Android 业务逻辑应该如何写(第二篇)》 ,觉得很有道理,这就是我想要的东西。

然后也看了文章里推荐的框架,觉得框架虽好,但功能太简单,已经不符合现在android开发的需求了,大佬又没代码发出来,所以只能自己试着写一个,现在将它分享出来(建议先看看大佬的文章,不然可能不知道我下面说什么)。

代码仓库

ShatterManager

概述

首先,总体的思想是将复杂的业务进行拆分打散,拆解成一个个的"碎片",每个碎片管理着一个或者多个相关功能,然后碎片之间相对独立,互不耦合,从而达到一个比较好的架构模式。

这种思想我相信大家都知道,缺少的只是一个通用的,统一管理的东西而已。

在我的设计里,一个业务模块可以拆成多个碎片,每个碎片又可以拆成多个子碎片,每个子碎片也可以继续拆,形成一课树的结构。这样在使用的时候就可以根据自己把握的粒度去拆了。

碎片功能

  1. 每个碎片,在使用的时候应该要和使用 Activity 一样,没什么差别,这样就不会有学习成本
  2. 某一个碎片可以方便的找到碎片树中任意一个碎片实例,这样可以相互调用对方的方法
  3. 除了主动调用,应该也有事件通知,当然可以用EventBus,但个人觉得又要注册解注册,又要写注解的,贼麻烦,应该有一个自己的才行。
  4. 碎片除了能拆分代码,应该也要具有拆分布局的功能,也就是跟 Fragment 一样。

这里想起大佬文章里面推荐框架里的一个有趣提问:大概意思是为啥要搞这些玩意,直接用 Fragment 不行?

首先在我自己在实际项目中重构的过程中发现,一般复杂模块的拆分,很有可能会拆出二三十个碎片出来,如果都用 fragment,意味着你要管理二三十个,何况 fragment 的管理是通过 fragmentmanager 进行的,想想都觉得麻烦。何况 fragmentmanager 是系统的类,你觉得你写得没问题,不保证系统什么时候觉得你有问题就崩了。

第二个就是通讯问题,貌似除了EventBus,加全局(Activity级)的 ViewModel 和 LiveData,也没什么好方法,还有抽出接口方法来调用的,都是挺麻烦而且不推荐的方式。

第三个是fragment一般也是View的容器,就是一个fragment通常会跟着一个布局,但实际当拆分中,并不一定需要,比如我这个碎片就一个上传功能,虽然fragment也可以无布局,但感觉有点杀鸡用牛刀的 feel。

总结来说就是
1. 碎片具有fragment的功能,但不止有fragment的功能。
2. 碎片的粒度比fragment更小,就是粒度上fragment大于碎片,功能上碎片大于fragment

代码设计

下面简单介绍下主要功能的部分代码

每个碎片我们用 Shatter 表示,他们都统一由 ShatterManager 去管理。

kotlin 复制代码
class ShatterManager(internal val activity: AppCompatActivity)  {  
     internal val shatters = mutableListOf<Shatter>()
     //......
}

在ShatterManager中,要求传入 AppCompatActivity,之所以是它不是 Activity,原因是可以方便的使用 lifecycle 和 lifecycleScope 等,这些在 Activity 里是没有的。然后碎片用一个 list 去保管。

kotlin 复制代码
fun addShatter(@IdRes containViewId: Int, shatter: Shatter) = apply {  
    shatter.shatterManager = this  
    val containView = activity.findViewById<View>(containViewId)  
    addShatter(containView, shatter)  
}  
  
fun addShatter(containView: View, shatter: Shatter) = apply {  
    shatter.shatterManager = this  
    shatter.containView = containView  
    shatter.attachActivity(activity)  
    shatters.add(shatter)   
}  
  
fun addShatter(shatter: Shatter) = apply {  
    shatter.shatterManager = this  
    shatter.attachActivity(activity)  
    shatters.add(shatter)   
}

添加碎片提供了 3 个方法,如果你想碎片也当作 View 容器,就用前 2 个,如果只是拆分逻辑代码,就用第 3 个。

碎片 Shatter

kotlin 复制代码
open class Shatter {
    companion object {  
      const val NO_LAYOUT = 0  
    }
    
    @LayoutRes  
    open fun getLayoutResId(): Int = NO_LAYOUT  

    open fun getTag(): String = this::class.java.simpleName
}

在 Shatter 中,有2个基本方法,一个是 getLayoutResId ,如果你的碎片是当作View容器的,那么就要重写它,传入布局。getTag 是每个碎片的唯一标记,用于相互查找,一般没事不用管它。

生命周期

接下来看碎片的生命周期管理。

kotlin 复制代码
fun attachActivity(activity: AppCompatActivity?) {
  onAttachActivity(activity)  
  if (getLayoutResId() != NO_LAYOUT && containView != null) {  
      if (containView is ViewGroup) {  
         val view = LayoutInflater.from(activity).inflate(getLayoutResId(), null)  
         (containView as ViewGroup).addView(view) 
         containView = view  
      }  
  }  
  onShatterCreate()
}

fun onAttachActivity(activity: AppCompatActivity?) {  
    this.act = activity  
}

private fun onShatterCreate() {  
    onCreate(activity.intent)  
    initView(containView, activity.intent)  
    initData(activity.intent)  
}

open fun onCreate(intent: Intent?) {}  
open fun initView(view: View?, intent: Intent?) {}  
open fun initData(intent: Intent?) {}

最开始是 attachActivity,它在添加碎片的时候就会调用,在这里如果是View容器的话,它会将布局添加到对应的ViewGroup中,然后分出了3个方法可以重写,onCreate,initView,initData,你可以根据他们的调用顺序合理的重写他们。

其他生命周期,我们通过注册 ActivityLifecycleCallbacks 去实现,简单看下代码就行:

kotlin 复制代码
class ShatterActivityWatcher(val currAct: Activity) {
    private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
        override fun onActivityStarted(activity: Activity) {  
            if (activity == currAct) {   
                dispatch { it -> it.shatters.forEach { it.onStart() } }  
            }
        }
        //......
    }

    fun dispatch(callback: (ShatterManager) -> Unit) {  
        if (currAct is IShatterActivity) {  
            val manager = currAct.getShatterManager()  
            callback.invoke(manager)  
        }  
    }

    fun install() {  
       getApplication().registerActivityLifecycleCallbacks(lifecycleCallbacks)  
    }  
  
    fun uninstall() {  
       getApplication().unregisterActivityLifecycleCallbacks(lifecycleCallbacks)  
    }
}

使用 ShatterManager 的 Activity,需要实现 IShatterActivity 接口,里面就一个 getShatterManager 方法,为的就是能在碎片中方便的使用 ShatterManager.

然后可以通过 lifecycle 去注册或者自动解注册了:

kotlin 复制代码
class ShatterManager(internal val activity: AppCompatActivity) : LifecycleEventObserver {
    init {  
        activity.lifecycle.removeObserver(this)  
        activity.lifecycle.addObserver(this)  
        activityWatcher.install()
    }
    
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 
        if (event == Lifecycle.Event.ON_DESTROY) {  
            activityWatcher.onDestroy()  
            activityWatcher.uninstall()  
            source.lifecycle.removeObserver(this)  
            return  
        }  
    }
}

ViewBinding

在 initView 中,可以通过 view.findViewbyId 去操作view,同样的也可以通过 getBinding 方法去获取对应的 ViewBinding 操作:

kotlin 复制代码
@PublishedApi
internal var viewBinding: ViewBinding? = null

inline fun <reified B : ViewBinding> getBinding(): B {
    return if (viewBinding == null) {
        val method = B::class.java.getMethod("bind", View::class.java)
        val viewBinding = method.invoke(null, containView) as B
        this.viewBinding = viewBinding
        viewBinding
    } else {
        viewBinding as B
    }
}

//使用
private val binding
    get() = getBinding<ShatterALayoutBinding>()

子碎片

一开始说到,碎片是一颗树的结构,子碎片我放在了 Shatter 里面:

kotlin 复制代码
open class Shatter {
    internal val childShatters = mutableListOf<Shatter>()
    
    fun addChildShatters(shatter: Shatter) = apply {
        childShatters.add(shatter)
    }

    fun addChildShatters(containView: View, shatter: Shatter) = apply {
        shatter.containView = containView
        childShatters.add(shatter)
    }
}

这样在添加的时候层级会比较鲜明:

kotlin 复制代码
 getShatterManager().addShatter(
     ShatterA()
       .addChildShatter(ShatterAChildA())
       .addChildShatter(ShatterAChildB())
       //......
 )

生命周期的管理也相对简单,只需要在对应回调里面向下分发一下就好:

kotlin 复制代码
override fun onStart() {  
    childShatters.forEach { it.onStart() }  
}

事件通知

事件通知我这里选用了 Flow 去操作:

kotlin 复制代码
class ShatterManager(internal val activity: AppCompatActivity) : LifecycleEventObserver {
    private val newMsgFlow: MutableSharedFlow<ShatterEvent<*>> by lazy {  
        MutableSharedFlow()  
    }

    init {
        activity.lifecycleScope.launch {
            newMsgFlow.collectLatest { event ->
                shatters.forEach { it.onShatterEvent(event.key, event.data) }
            }
        }
    }
    
    open fun onShatterEvent(key: String, data: Any?) {  
        childShatters.forEach { it.onShatterEvent(key, data) }  
    }
    
    internal fun sendShatterEvent(key: String, data: Any? = null) {
        activity.lifecycleScope.launch {
            newMsgFlow.emit(ShatterEvent(key, data))
        }
    }
}

这样,发送事件使用 sendShatterEvent,哪个碎片想要处理,只需要重写 onShatterEvent 就可以。

碎片查找

由于碎片结构是一颗树,有很多分支,那么要查找任意一个碎片实例的话,用遍历似乎有很大的性能问题。记得算法里面有一种解法是在已知答案中查找,所以在添加碎片的过程中,我们把碎片实例顺便存在了一个 hashMap 里:

kotlin 复制代码
fun addShatter(containView: View, shatter: Shatter) = apply {  
    //......
    ShatterCache.putShatter(shatter)
    //......
}

object ShatterCache {
    private val cacheMap = hashMapOf<String, Shatter>()

    fun putShatter(shatter: Shatter) {
        cacheMap[shatter.getTag()] = shatter
    }

    fun getShatter(tag: String): Shatter? {
        return cacheMap[tag]
    }

    fun removeShatter(tag: String) {
        cacheMap.remove(tag)
    }

    fun clear(){
        cacheMap.clear()
    }
}

那么我查找的时候只需要直接在 hashMap 中取出来就行了,避免了使用遍历,性能上也是OK的:

kotlin 复制代码
fun <T : Shatter> findShatter(clazz: Class<T>): T? {
    val tag = clazz.simpleName
    val shatter = shatters.find { it.getTag() == tag } ?: return null
    return shatter as T
}

//使用
val shatter get() = shatterManager.findShatter(ShatterA::class.java)

实战效果

下面以本人实际项目中聊天页面里的顶部bar部分来看看实际效果:

首先看看顶部的 bar 有什么功能:

左边有返回按钮,等级显示,等级那还有个动画,右边是一个聊天次数的进度,没次数了还要播个动画,还有一个分享按钮,点击分享按钮,左右两边都隐藏,中间显示呢称。

在没重构之前,这部分的 xml 布局有137行,整个界面的 xml 有 400 来行, 代码部分就是拉接口,然后UI变化,控件的显示隐藏以及点击事件等等逻辑,一共也 300 快 400 行

尽管UI逻辑都在avtivity,业务逻辑都在viewmodel,但是还是写得挺复杂。

然后开始重构:

我将它拆出来一个 TopShatter ,下面有 2 个子 Shatter,为左边的 LevelShatter ,右边的 ChatNumShatter

TopShatter 主要处理返回事件,分享事件,以及控件的显示隐藏等

LevelShatter 主要处理左边的等级变化逻辑

ChatNumShatter 主要处理右边的聊天次数等

kotilt 复制代码
getShatterManager().addShatter(
      R.id.topShatterLayout, TopShatter()
                                .addChildShatter(LevelShatter())
                                .addChildShatter(ChatNumbShatter())
)

这样,层级就很分明了,要修改什么功能可以直接找到。

原来的 100 多行的 bar 布局抽出来单独一个xml,然后用 topShatterLayout 代替,添加到 TopShatter 中:

👇👇👇👇👇

kotlin 复制代码
class TopShatter : Shatter() {
    override fun getLayoutResId(): Int = R.layout.shatter_top_bar
    
    val binding get() = getBinding<ShatterTopBarBinding>()
    
    override fun initView(view: View?, intent: Intent?) {
      super.initView(view, intent)
      //.....
    }
}

点击事件就不说了,控件显示隐藏因为除了顶部栏,界面其他地方也需要有变化,所以统一采用了事件方式通知,比如点击分享按钮时发一个事件:

kotlin 复制代码
sendShatterEvent(ShatterEventKey.KEY_UPDATE_SELECT_STATE,SelectState(isMultiSelect = true))

然后需要接收:

kotlin 复制代码
override fun onShatterEvent(key: String, data: Any?) {
    super.onShatterEvent(key, data)
    if (key == ShatterEventKey.KEY_UPDATE_SELECT_STATE && data != null) {
        val info = data as SelectState
        if (info.isMultiSelect) {
           //......
        } else {
           //.....
        }
    }
}

然后 LevelShatter: 因为拆布局的时候没拆很细,都在 TopShatter 里面了,那 LevelShatter 就不需要重写 getLayoutResId 方法了,所以如果我想使用控件怎么办?可以直接这样使用:

kotlin 复制代码
private val binding
    get() = findShatter(TopShatter::class.java)?.getBinding<ShatterTopBarBinding>()

通过 findShatter 找到它,然后获取 binding 即可。

然后就是对应的逻辑处理了。没个碎片还可以对应一个独立的 ViewModel,但他们可以都是属于 Activity 级的,比如:

kotlin 复制代码
private val viewModel by lazy { activity.viewModels<LevelShatterViewModel>().value }

右边那个 ChatNumShatter 同理。

可以看到通过这样拆分之后,功能点和功能点的逻辑,对应的 viewmodel 等之间都隔离开了,各自处理自己的事情,通过 事件 event 或者 findShatter 去相互通信。

当你某个需求要改动这些功能点时,可以快速的找到对应的代码,并修改,而不影响其他。

这在多人协作的项目中就有效避免了多个人同时做一个界面需求的时候各种代码冲突的问题。

在本人实际项目中,聊天界面功能多,逻辑杂,全部加起来估计都有两千多行,每次改功能找代码都很麻烦,重构过后被我分成了20来个碎片,每个碎片的代码不超过200行,而且功能明确,一下就能找到了,我觉得还是可以的。

就这些,希望你能看明白我在说啥,和能帮助到你!

相关推荐
K1t023 分钟前
Android-UI设计
android·ui
吃汉堡吃到饱2 小时前
【Android】浅析MVC与MVP
android·mvc
深海呐8 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang8 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼8 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
canonical_entropy9 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
落落落sss10 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
沛沛老爹11 小时前
服务监控插件全览:提升微服务可观测性的利器
微服务·云原生·架构·datadog·influx·graphite
huaqianzkh12 小时前
了解华为云容器引擎(Cloud Container Engine)
云原生·架构·华为云
消失的旧时光-194312 小时前
kotlin的密封类
android·开发语言·kotlin