从0搭建一个实用的MVVM框架

本文分享如何利用Jetpack组件(LiveData、ViewModel、Lifecycle、Navigation)构建一个快速开发的MVVM框架。框架支持动态加载多状态布局,提供Activity、Fragment生成模板,实现网络请求的便捷处理。通过模块化设计,分为MVVMLibrary、MVVMNavigationLibrary、MVVMNetworkLibrary,提供一键生成代码模板,简化开发流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结合Jetpack,构建快速开发的MVVM框架。

项目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation组件。

支持动态加载多状态布局:加载中、成功、失败、标题;

支持快速生成ListActivity、ListFragment;

支持使用插件快速生成适用于本框架的Activity、Fragment、ListActivity、ListFragment。

前言

随着GoogleJetpack的完善,对于开发者来说,MVVM显得越来越高效与方便。

对于使用MVVM的公司来说,都有一套自己的MVVM框架,但是我发现有些只是对框架进行非常简单的封装,导致在开发过程中会出现很多没必要的冗余代码。

这篇文章主要就是分享如何从0搭建一个高效的MVVM框架。

基于MVVM进行快速开发, 上手即用。(重构已完成,正在编写SampleApp)

对基础框架进行模块分离, 分为 MVVM LibraryMVVM Navigation LibraryMVVM Network Library
可基于业务需求使用 MVVM LibraryMVVM Navigation LibraryMVVM Network Library

已开发一键生成代码模板, 创建适用于本框架的Activity和Fragment.

如何集成

To get a Git project into your build:

Step 1. Add the JitPack repository to your build file

Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
    // BaseMVVM 同时集成了 MVVM、MVVM Network、MVVM Navigation
    implementation 'com.github.Chen-Xi-g.MVVMFramework:base_mvvm:Tag'
    // MVVM 基类
    implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_framework:Tag'
    // MVVM Network 只负责网络处理
    implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag'
    // MVVM Navigation 组件抽离
    implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag'
}

依赖引入后,需要初始化依赖,下面是模块化初始化流程。

合并集成

合并集成只需要引入BaseMVVM即可,内部 默认集成了MVVMMVVMNetworkMVVMNavigation,只需要实现com.alvin.base_mvvm.base.IBaseMVVM接口就可以直接使用。

1.实现IBaseMVVM接口

建议在你的Application类,实现IBaseMVVM,并且需要在合适的位置进行配置和初始化相关参数,可以在这里配置网络请求框架的参数和UI全局参数。比如拦截器和多域名,全局Activity和Fragment属性。

完整代码:

class SampleApplication2 : Application(), IBaseMVVM {
    override fun onCreate() {
        super.onCreate()
        // 使用默认配置
        initBaseMVVM(this, "https://www.wanandroid.com/")
        // 自定义配置
        initBaseMVVM(
            this,
            "https://www.wanandroid.com/",
            BaseActivitySetting(),
            BaseFragmentSetting(),
            isDebug = BuildConfig.DEBUG,
            timeUnit = TimeUnit.SECONDS,
            timeout = 30,
            retryOnConnection = true,
            domain = {
                Constant.domainList.forEach { map ->
                    map.forEach {
                        if (it.key.isNotEmpty() && it.value.isNotEmpty()) {
                            put(it.key, it.value)
                        }
                    }
                }
            },
            // 是否打印
            hideVerticalLine = true,
            // 请求标识
            requestTag = "Request 请求参数",
            // 响应标识
            responseTag = "Response 响应结果",
            // 拦截器
            ResponseInterceptor(),
            ParameterInterceptor()
        )
    }
}

对于ViewModel的网络请求已实现响应的扩展函数,参考单独集成2的使用。

单独集成

1.实现IMVVM、INetWork接口

建议在你的Application类,实现IMVVM、INetWork,并且需要在合适的位置进行配置和初始化相关参数,可以在这里配置网络请求框架的参数和UI全局参数。比如拦截器和多域名,全局Activity和Fragment属性。

完整代码:

class SampleApplication : Application(), IMVVMApplication, INetWorkApplication {

    override fun onCreate() {
        super.onCreate()
        // 初始化MVVM框架
        initMVVM(this, BaseActivitySetting(), BaseFragmentSetting(), BuildConfig.DEBUG)
        /* 两种配置网络请求,选择其一即可 */
        // 初始化网络请求,默认配置
        initNetwork(baseUrl = "https://www.wanandroid.com")
        // 初始化网络请求, 自定义配置
        initNetwork(
            // 基础url
            "https://www.wanandroid.com",
            // 时间单位
            TimeUnit.SECONDS,
            // 时间
            30,
            // 是否重试
            true,
            // 多域名配置
            domain = {
                Constant.domainList.forEach { map ->
                    map.forEach {
                        if (it.key.isNotEmpty() && it.value.isNotEmpty()) {
                            put(it.key, it.value)
                        }
                    }
                }
            },
            // 是否隐藏网络请求中的竖线
            true,
            // 请求标识
            "Request 请求参数",
            // 响应表示
            "Response 响应结果",
            // 是否Debug
            BuildConfig.DEBUG,
            // 拦截器
            ResponseInterceptor(),
            ParameterInterceptor()
        )
    }
}

2.创建ViewModel扩展函数

所有模块需要依赖的base模块创建ViewModel相关的扩展函数VMKxt和Json实体类壳BaseEntity

/**
 * 过滤服务器结果,失败抛异常
 * @param block 请求体方法,必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不传
 * @param isLoading 是否显示 Loading 布局
 * @param loadingMessage 加载框提示内容
 */
fun <T> BaseViewModel.request(
    block: suspend () -> BaseResponse<T>,
    success: (T?) -> Unit,
    error: (ResponseThrowable) -> Unit = {},
    isLoading: Boolean = false,
    loadingMessage: String? = null
): Job {
    // 开始执行请求
    httpCallback.beforeNetwork.postValue(
        // 执行Loading逻辑
        LoadingEntity(
            isLoading,
            loadingMessage?.isNotEmpty() == true,
            loadingMessage ?: ""
        )
    )
    return viewModelScope.launch {
        kotlin.runCatching {
            //请求体
            block()
        }.onSuccess {
            // 网络请求成功, 结束请求
            httpCallback.afterNetwork.postValue(false)
            //校验请求结果码是否正确,不正确会抛出异常走下面的onFailure
            kotlin.runCatching {
                executeResponse(it) { coroutine ->
                    success(coroutine)
                }
            }.onFailure { error ->
                // 请求时发生异常, 执行失败回调
                val responseThrowable = ExceptionHandle.handleException(error)
                httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
                responseThrowable.errorLog?.let { errorLog ->
                    LogUtil.e(errorLog)
                }
                // 执行失败的回调方法
                error(responseThrowable)
            }
        }.onFailure { error ->
            // 请求时发生异常, 执行失败回调
            val responseThrowable = ExceptionHandle.handleException(error)
            httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
            responseThrowable.errorLog?.let { errorLog ->
                LogUtil.e(errorLog)
            }
            // 执行失败的回调方法
            error(responseThrowable)
        }
    }
}

/**
 * 不过滤服务器结果
 * @param block 请求体方法,必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不传
 * @param isLoading 是否显示 Loading 布局
 * @param loadingMessage 加载框提示内容
 */
fun <T> BaseViewModel.requestNoCheck(
    block: suspend () -> T,
    success: (T) -> Unit,
    error: (ResponseThrowable) -> Unit = {},
    isLoading: Boolean = false,
    loadingMessage: String? = null
): Job {
    // 开始执行请求
    httpCallback.beforeNetwork.postValue(
        // 执行Loading逻辑
        LoadingEntity(
            isLoading,
            loadingMessage?.isNotEmpty() == true,
            loadingMessage ?: ""
        )
    )
    return viewModelScope.launch {
        runCatching {
            //请求体
            block()
        }.onSuccess {
            // 网络请求成功, 结束请求
            httpCallback.afterNetwork.postValue(false)
            //成功回调
            success(it)
        }.onFailure { error ->
            // 请求时发生异常, 执行失败回调
            val responseThrowable = ExceptionHandle.handleException(error)
            httpCallback.onFailed.value = responseThrowable.errorMsg ?: ""
            responseThrowable.errorLog?.let { errorLog ->
                LogUtil.e(errorLog)
            }
            // 执行失败的回调方法
            error(responseThrowable)
        }
    }
}

/**
 * 请求结果过滤,判断请求服务器请求结果是否成功,不成功则会抛出异常
 */
suspend fun <T> executeResponse(
    response: BaseResponse<T>,
    success: suspend CoroutineScope.(T?) -> Unit
) {
    coroutineScope {
        when {
            response.isSuccess() -> {
                success(response.getResponseData())
            }
            else -> {
                throw ResponseThrowable(
                    response.getResponseCode(),
                    response.getResponseMessage(),
                    response.getResponseMessage()
                )
            }
        }
    }
}

以上代码封装了快速的网络请求扩展函数,并且可以根据自己的情况,选择脱壳或者不脱壳的回调处理。 调用示例:

/**
 * 加载列表数据
 */
fun getArticleListData(page: Int, pageSize: Int) {
    request(
        {
            filterArticleList(page, pageSize)
        }, {
            // 成功操作
            it?.let {
                _articleListData.postValue(it.datas)
            }
        }
    )
}

完成上面的操作,你就可以进入愉快的开发工作了。

引入一键生成代码插件(可选)

每次创建Activity、Fragment、ListActivity、ListFragment都是重复的工作,为了可以更高效的开发,减少这些枯燥的操作,特地编写的快速生成MVVM代码的插件,该插件只适用于当前MVVM框架,具体使用请前往AlvinMVVMPlugin。集成后你就可以开始像创建EmptyActivity这样创建MVVMActivity

框架结构

mvvm

该组件对Activity和Fragment进行常用属性封装

  • base包下封装了MVVM的基础组件。
    • activity实现DataBinding + ViewModel的封装,以及一些其他功能。
    • adapter实现DataBinding + Adapter的封装。
    • fragment实现DataBinding + ViewModel的封装,以及一些其他功能。
    • livedata实现LiveData的基础功能封装,如基本数据类型的非空返回值。
    • view_model实现BaseViewModel的处理。
  • help包下封装了组件的辅助类,在BaseApplication中进行全局Actiivty、Fragment属性赋值。
  • manager包下封装了对Activity的管理。
  • utils包下封装了LogUtil工具类,通过BaseApplication进行初始化。
Activity封装
  1. AbstractActivityActivity的抽象基类,这个类里面的方法适用于全部Activity的需求。
    该类中封装了所有Activity必须实现的抽象方法。
  2. BaseActivity封装了基础的Activity功能,主要用来初始化Activity公共功能:DataBinding的初始化、沉浸式状态栏、AbstractActivity抽象方法的调用、屏幕适配、空白区域隐藏软键盘。具体功能可以自行新增。
  3. BaseDialogActivity只负责显示Dialog Loading弹窗,一般在提交请求或本地流处理时使用。也可以扩展其他的Dialog,比如时间选择器之类。
  4. BaseContentViewActivity是对布局进行初始化操作的Activity,他是我们的核心。这里处理了每个Activity的每个状态的布局,一般情况下有:
    • TitleLayout 公共标题
    • ContentLayout 主要的内容布局,使我们需要程序内容的主要容器。
    • ErrorLayout 当网络请求发生错误,需要对用户进行友好的提示。
    • LoadingLayout 正在加载数据的布局,给用户一个良好的体验,避免首次进入页面显示的布局没有数据。
  5. BaseVMActivity实现ViewModeActivity基类,通过泛型对ViewModel进行实例化。并且通过BaseViewModel进行公共操作。
  6. BaseMVVMActivity 所有Activity最终需要继承的MVVM类,通过传入DataBindingViewModel的泛型进行初始化操作,在构造参数中还需要获取Layout布局
  7. BaseListActivity适用于列表的Activity,分页操作、上拉加载、下拉刷新、空布局、头布局、底布局封装。
Fragment封装

根据你的需要进行不同的封装,我比较倾向于和Activity具有相同功能的封装,也就是Activity封装的功能我Fragment也要有。这样在使用Navigation的时候可以减少ActivityFragment的差异。这里直接参考Activity的封装

Adapter封装

每个项目中肯定会有列表的页面,所以还需要对Adapter进行DataBinding适配,这里使用的Adapter是BRVAH

abstract class BaseBindingListAdapter<T, DB : ViewDataBinding>(
    @LayoutRes private val layoutResId: Int
) : BaseQuickAdapter<T, BaseViewHolder>(layoutResId) {

    abstract fun convert(holder: BaseViewHolder, item: T, dataBinding: DB?)

    override fun convert(holder: BaseViewHolder, item: T) {
        convert(holder, item, DataBindingUtil.bind(holder.itemView))
    }
}

LiveData封装

LiveData在使用的时候会出现数据倒灌的情况,用简单的话来描述数据倒灌:A订阅1月1日新闻信息,B订阅1月15日新闻信息,但是B在1月15日同时收到了1月1日的信息,这明显不符合我们生活中的逻辑,所以需要对LiveData进行封装。

Navigation封装

通过重写 FragmentNavigator 将原来的 FragmentTransaction.replace() 方法替换为 hide()/Show()

ViewModel封装

BaseViewModel中封装一个网络请求需要用的LiveData,下面是一个简单的示例

open class BaseViewModel : ViewModel() {

    // 默认的网络请求LiveData
    val httpCallback: HttpCallback by lazy { HttpCallback() }

    inner class HttpCallback {

        /**
         * 请求发生错误
         *
         * String = 网络请求异常
         */
        val onFailed by lazy { StringLiveData() }

        /**
         * 请求开始
         *
         * LoadingEntity 显示loading的实体类
         */
        val beforeNetwork by lazy { EventLiveData<LoadingEntity>() }

        /**
         * 请求结束后框架自动对 loading 进行处理
         *
         * false 关闭 loading or Dialog
         * true 不关闭 loading or Dialog
         */
        val afterNetwork by lazy { BooleanLiveData() }
    }
}

辅助类封装

大部分的ActivityFragment样式基本相同,比如布局中的TitleLayoutLoadingLayout这些都是统一样式。所以可以封装全局的辅助类来对Activity中的属性进行抽离。

  • 定义接口ISettingBaseActivity添加抽离的方法,并且赋于默认值。
  • 定义接口ISettingBaseFragment添加抽离的方法,并且赋于默认值。
  • 创建ISettingBaseActivityISettingBaseFragment的实现类,进行默认的自定义操作。
  • 创建GlobalMVVMBuilder进行赋值
管理类封装

通过Lifecycle结合AppManager对Activity的进出栈管理。

mvvm_navigation

分离Navigation,通过重写 FragmentNavigator 将原来的 FragmentTransaction.replace() 方法替换为 hide()/Show()。

mvvm_network

使用 Retrofit + OkHttp + Moshi 对网络请求进行封装,使用密封类自定义异常处理。

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

需要的小伙伴直接点击文末小卡片免费领取哦,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)

Android学习PDF+架构视频+面试文档+源码笔记

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

领取地址:可点击下方优快云官方认证卡片免费获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值