App架构设计之BaseActivity(3)

1,837 阅读3分钟

App架构设计之BaseActivity(2)

上回对BaseActivity做了大体的介绍,接下来分析具体代码如何实现;

注意哈,这里提到的BaseActivity不是指某个类,而是一组功能的组合;

1.整体架构图

classDiagram
BaseActivity <|-- BaseVMActivity
BaseVMActivity <|-- AppBaseVMActivity
AppBaseVMActivity <|-- MainActivity
class BaseActivity{

+initView()
+initData()
+requestData()
}
class BaseVMActivity{
-BaseViewModel mViewModel
-ViewBinding mBinding
}
class AppBaseVMActivity{
+X x
+XX xx
+x()
}
class MainActivity{
+XXXX xxxx
+XXXXX xxxxx
+xx()
}

2. 代码实现

2.1 BaseActivity实现

它主打统一代码模板&通用的逻辑处理,具体分析如下:

  • 注释1,实现通用的接口,例如View.OnClickListener;
  • 注释2,开发的页面都会自定义背景,所以系统自带的背景就可以去掉,防止过度渲染;
  • 注释3,用过LoadSir的都知道,当接口请求数据失败,可以设置一个失败的页面,点击页面触发重新请求,这就是重新请的处理,由子类实现;
  • 注释4,也是跟LoadSir相关的,提供一个注册的view,如果子类用不到LoadSir,可以不用重写注释3、4这两个函数;
  • 注释5,这三个函数的就是模板代码,初始化view、初始化数据、请求数据;
  • 注释6,细分页面回退方式,巧妙地利用实现空接口来区分,具体可以注释11的定义,可根据实际情况进行调整;
  • 注释7,处理点击页面关闭输入框;
abstract class BaseActivity : FragmentActivity(), View.OnClickListener {//1

    val TAG by lazy {
        this@BaseActivity::class.java.simpleName
    }

    var lastClickTime = 0L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.decorView.background = null//防止过渡渲染 //2
        bindActivity()
        initView()
        initData()
        requestData()
    }

    open fun loadSirReload() {}//3

    open fun getRegisterView() = this//4

    //做view初始化工作
    abstract fun initView()//5
    //初始化数据
    abstract fun initData()//5
    //设置本地数据&请求网络数据
    abstract fun requestData()//5
  
    override fun onClick(v: View?) {}
    
    override fun onBackPressed() {//6
        when (this) {
            is PageType.TypeDisableBackScreen -> {}//禁止返回
            is PageType.TypeTwoTimesBackScreen -> {
                if (System.currentTimeMillis() - lastClickTime > 2000) {//连续点击返回
                    lastClickTime = System.currentTimeMillis();
                } else {
                    super.onBackPressed()
                }
            }
            else -> {
                super.onBackPressed()
            }
        }
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {//7
        if (ev?.action == MotionEvent.ACTION_DOWN) {
            KeyboardUtils.hideSoftInput(this@BaseActivity)
        }
        return super.dispatchTouchEvent(ev)
    }
}

2.2 BaseVMActivity实现

它主打对mvvn架构实现处理,具体分析如下:

  • 注释1,继承Basectivity,且定义泛型类型VM,VB,子类传入对应类型,会自动通过注释2、注释3进行构建;
  • 注释3,构建App级别的ViewModel,所有页面模块公用一个;
  • 注释4,绑定对BaseViewModel liveData的监听;
  • 注释5,BaseViewModel 中定义了toast、loadingDialog、loadSir相关的LiveData,页面负责监听;
abstract class BaseVMActivity3<VM : BaseViewModel, VB : ViewBinding> : Basectivity() {//1

     val mBinding: VB by viewBinding(getViewBindingClass())//2
     val mViewModel: VM by viewModel(getViewModelClass())//3

    ```
    private fun getViewBindingClass(): Class<VB> {
        return getClazz(1) as Class<VB>
    }

    private fun getViewModelClass(): Class<VM> {
        return getClazz(0) as Class<VM>
    }

    private fun getClazz(index: Int): Class<*> {
        val parameterizedType =
            if ((javaClass.genericSuperclass as? ParameterizedType) == null) {//兼容FamilyMembersActivity继承MembersActivity
                javaClass.superclass.genericSuperclass
            } else {
                javaClass.genericSuperclass
            }
        return (parameterizedType as ParameterizedType).actualTypeArguments[index] as Class<*>
    }

    val mAppViewModel by applicationViewModels<AppViewModel2>()//3

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        startObserve()
    }

    open fun startObserve() {//4
        bindObserve(mViewModel)
    }

    fun bindObserve(viewModel: BaseViewModel){
        viewModel.toastState.observe(this) {//5
            showToast(it)
        }
        viewModel.loadingDialogState.observe(this) {//5
            when (it) {
                is BaseViewModel.LoadingDialogState.Showing -> {
                    showLoadingDialog(it.params?.first, it.params?.second ?: true)
                }
                else -> {
                    dismissLoadingDialog()
                }
            }
        }
        viewModel.pageState.observe(this) {//5
            when (it) {
                is BaseViewModel.PageState.Loading -> {
                    showPageLoading()
                }
                is BaseViewModel.PageState.Failed -> {
                    showPageFailed()
                }
                is BaseViewModel.PageState.Success -> {
                    showPageSuccess()
                }
                is BaseViewModel.PageState.NoNet -> {
                    showPageNoNet()
                }

            }
        }
    }
}
2.3 AppBaseVMActivity实现

它主打处理跟业务相关的:

  • 注释1、2,都是跟业务相关的,就不多做解释了;
abstract class AppBaseVMActivity<VM : BaseViewModel, VB : ViewBinding> : BaseVMActivity<VM,VB>(){

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       (this.mViewModel as? IConsultInfoRequest)?.apply {//1
           if (pageId != -1 && this@AppBaseVMActivity is PageType.TypeShowConsult) {
               ConsultHelper(pageId = pageId, this@AppBaseVMActivity, this)
           }
       }
       PageStateEventHelper(this@AppBaseVMActivity)//2
   }
}
2.4 扩展函数

采用扩展的方式来丰富FragmentActivity的能力,相当于组合,比把函数封装到BaseActivity更厉害一些。 Toast相关的扩展函数;

fun FragmentActivity.showToast(msg:String,isLongDuration:Boolean = false){
    ToastUtils.showToast(this, msg)
}

fun cancelToast() {
    ToastUtils.cancelToast()
}

Dilaog相关的扩展函数;

fun FragmentActivity.showLoadingDialog(msg: String? = "loading...", cancelAble: Boolean = true) {
    if (this.isLoadingDialogShowing()) {//多次触发,只显示一次
        return
    }
    try {
        dismissDialogFragment(FragmentLoadingDialog.TAG)
        this.supportFragmentManager.beginTransaction()
            .add(FragmentLoadingDialog(msg), FragmentLoadingDialog.TAG)
            .commitAllowingStateLoss()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

fun FragmentActivity.dismissLoadingDialog() {
    dismissDialogFragment(FragmentLoadingDialog.TAG)
}

fun FragmentActivity.isLoadingDialogShowing(): Boolean {
    return this.supportFragmentManager?.findFragmentByTag(FragmentLoadingDialog.TAG) != null
}

suspend fun FragmentActivity.showInfoDialog(params: FragmentInfoDialog.Params): BaseDialogFragment.Action {
    dismissInfoDialog()
    FragmentInfoDialog().also {
        it.bindParams(arrayMapOf("data" to params))
        return it.showDialog(this.supportFragmentManager)
    }
}

fun FragmentActivity.dismissInfoDialog() {
    this.supportFragmentManager.findFragmentByTag(FragmentInfoDialog.TAG)
        ?.apply {
            (this as? DialogFragment)?.dismissAllowingStateLoss()
        }
}

fun FragmentActivity.isInfoDialogShowing(): Boolean {
    return this.supportFragmentManager.findFragmentByTag(FragmentInfoDialog.TAG) != null
}

2.5 viewBinding 、viewModel实现

用的都是Kotlin的扩展函数,也不是太复杂,具体代码如下:

fun <VB : ViewBinding> ComponentActivity.viewBinding(clazz: Class<VB>) = lazy {
    (clazz.getMethod("inflate", LayoutInflater::class.java)
        .invoke(null, layoutInflater) as VB).apply {
        setContentView(root)
    }
}

fun <VM : ViewModel> ComponentActivity.viewModel(clazz: Class<VM>) = lazy {
    ViewModelProvider(this).get(clazz)
}
2.6 总结

虽然分了三层,看起来有点多,但是每层职责都是很清晰,且都是可以替换的;

后续可能会出写一系列关于架构设计的内容,有兴趣的同学可以关注下。