结合Jetpack,构建快速开发的MVVM框架。
项目使用Jetpack:LiveData、ViewModel、Lifecycle、Navigation组件。
支持动态加载多状态布局:加载中、成功、失败、标题;
支持快速生成ListActivity、ListFragment;
支持使用插件快速生成适用于本框架的Activity、Fragment、ListActivity、ListFragment。
完整文章前往Github浏览
前言
随着
Jetpack
的完善,对于开发者来说,MVVM
显得越来越高效与方便。对于使用
MVVM
的公司来说,都有一套自己的MVVM
框架,但是我发现有些只是对框架进行非常简单的封装,导致在开发过程中会出现很多没必要的冗余代码。这篇文章主要就是分享如何从0搭建一个高效的
MVVM
框架。
基于MVVM进行快速开发, 上手即用。(重构已完成,正在编写SampleApp)
对基础框架进行模块分离, 分为
MVVM Library
--MVVM Navigation Library
--MVVM Network Library
可基于业务需求使用MVVM Library
、MVVM Navigation Library
、MVVM Network Library
已开发一键生成代码模板, 创建适用于本框架的Activity和Fragment. 具体查看AlvinMVVMPlugin_4_3
如何集成
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 {
// 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'
}
说明 | 依赖地址 | 版本号 |
---|---|---|
MVVM 基类 | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_framework:Tag' | Jetpack 1.0.7 |
MVVM Network | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_network:Tag' | Jetpack 1.0.7 |
MVVM Navigation | implementation 'com.github.Chen-Xi-g.MVVMFramework:mvvm_navigation:Tag' | Jetpack 1.0.7 |
依赖引入后,需要初始化依赖,下面是模块化初始化流程。
1.继承BaseApplication
创建你的Application类,继承BaseApplication
,并且需要在onCreate
函数中进行配置和初始化相关参数,可以在这里配置网络请求框架的参数和UI全局参数。比如拦截器和多域名,全局Activity和Fragment属性。
// 全局Activity设置
GlobalMVVMBuilder.initSetting(BaseActivitySetting(), BaseFragmentSetting())
private fun initHttpManager() {
// 参数拦截器
HttpManager.instance.setting {
// 设置网络属性
setTimeUnit(TimeUnit.SECONDS) // 时间类型 秒, 框架默认值 毫秒
setReadTimeout(30) // 读取超时 30s, 框架默认值 10000L
setWriteTimeout(30) // 写入超时 30s, 框架默认值 10000L
setConnectTimeout(30) // 链接超时 30s,框架默认值 10000L
setRetryOnConnectionFailure(true) // 超时自动重连, 框架默认值 true
setBaseUrl("https://www.wanandroid.com") // 默认域名
// 多域名配置
setDomain {
Constant.domainList.forEach { map ->
map.forEach {
if (it.key.isNotEmpty() && it.value.isNotEmpty()) {
put(it.key, it.value)
}
}
}
}
setLoggingInterceptor(
isDebug = BuildConfig.DEBUG,
hideVerticalLine = true,
requestTag = "HTTP Request 请求参数",
responseTag = "HTTP Response 返回参数"
)
// 添加拦截器
setInterceptorList(hashSetOf(ResponseInterceptor(), ParameterInterceptor()))
}
}
// 需要重写,传入当前是否初始Debug模式。
override fun isLogDebug(): Boolean {
// 是否显示日志
return BuildConfig.DEBUG
}
2.创建ViewModel扩展函数
所有模块需要依赖的base模块创建ViewModel相关的扩展函数VMKxt和Json实体类壳BaseEntity。
/**
* 过滤服务器结果,失败抛异常
* @param block 请求体方法,必须要用suspend关键字修饰
* @param success 成功回调
* @param error 失败回调 可不传
* @param isLoading 是否显示 Loading 布局
* @param loadingMessage 加载框提示内容
*/
fun BaseViewModel.request(
block: suspend () -> BaseResponse,
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 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 executeResponse(
response: BaseResponse,
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)
}
}
)
}
完成上面的操作,你就可以进入愉快的开发工作了。
3.引入一键生成代码插件(可选)
每次创建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封装
-
AbstractActivity
是Activity
的抽象基类,这个类里面的方法适用于全部Activity
的需求。 该类中封装了所有Activity必须实现的抽象方法。 -
BaseActivity
封装了基础的Activity
功能,主要用来初始化Activity
公共功能:DataBinding
的初始化、沉浸式状态栏、AbstractActivity
抽象方法的调用、屏幕适配、空白区域隐藏软键盘。具体功能可以自行新增。 -
BaseDialogActivity
只负责显示Dialog Loading
弹窗,一般在提交请求或本地流处理时使用。也可以扩展其他的Dialog
,比如时间选择器之类。 -
BaseContentViewActivity
是对布局进行初始化操作的Activity
,他是我们的核心。这里处理了每个Activity
的每个状态的布局,一般情况下有:-
TitleLayout
公共标题 -
ContentLayout
主要的内容布局,使我们需要程序内容的主要容器。 -
ErrorLayout
当网络请求发生错误,需要对用户进行友好的提示。 -
LoadingLayout
正在加载数据的布局,给用户一个良好的体验,避免首次进入页面显示的布局没有数据。
-
-
BaseVMActivity
实现ViewMode
的Activity
基类,通过泛型对ViewModel
进行实例化。并且通过BaseViewModel
进行公共操作。 -
BaseMVVMActivity
所有Activity
最终需要继承的MVVM
类,通过传入DataBinding
和ViewModel
的泛型进行初始化操作,在构造参数中还需要获取Layout
布局 -
BaseListActivity
适用于列表的Activity
,分页操作、上拉加载、下拉刷新、空布局、头布局、底布局封装。
Fragment封装
根据你的需要进行不同的封装,我比较倾向于和Activity
具有相同功能的封装,也就是Activity
封装的功能我Fragment
也要有。这样在使用Navigation
的时候可以减少Activity
和Fragment
的差异。这里直接参考Activity的封装
Adapter封装
每个项目中肯定会有列表的页面,所以还需要对Adapter
进行DataBinding
适配,这里使用的Adapter是BRVAH。
abstract class BaseBindingListAdapter(
@LayoutRes private val layoutResId: Int
) : BaseQuickAdapter(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
进行封装,详细的可以查看KunMinX
的**UnPeek-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() }
/**
* 请求结束后框架自动对 loading 进行处理
*
* false 关闭 loading or Dialog
* true 不关闭 loading or Dialog
*/
val afterNetwork by lazy { BooleanLiveData() }
}
}
辅助类封装
大部分的Activity
和Fragment
样式基本相同,比如布局中的TitleLayout
、LoadingLayout
这些都是统一样式。所以可以封装全局的辅助类来对Activity中的属性进行抽离。
- 定义接口
ISettingBaseActivity
添加抽离的方法,并且赋于默认值。 - 定义接口
ISettingBaseFragment
添加抽离的方法,并且赋于默认值。 - 创建
ISettingBaseActivity
和ISettingBaseFragment
的实现类,进行默认的自定义操作。 - 创建
GlobalMVVMBuilder
进行赋值
管理类封装
通过Lifecycle
结合AppManager
对Activity的进出栈管理。
mvvm_navigation
分离Navigation,通过重写 FragmentNavigator 将原来的 FragmentTransaction.replace() 方法替换为 hide()/Show()。
mvvm_network
使用 Retrofit
+ OkHttp
+ Moshi
对网络请求进行封装,使用密封类自定义异常处理。
推荐
价值100W+Android实战项目大全/高级UI/灵动的锦鲤/QQ空间热修复/插件化框架/组件化框架设计/网络访问框架/RXJava/IOC/MVVM/NDK_哔哩哔哩_bilibili
【Android源码解析】Android中高级架构进阶学习——百大框架源码解析Retrofit/OkHttp/Glide/RxJava/EventBus...._哔哩哔哩_bilibili
【Android framework教程】2022最新整理178P合集含framework面试题解析(Binder/Handler/AMS/WMS/插件化)_哔哩哔哩_bilibili
《Android framework开发揭秘》以及1932页《2022最新Android中高级面试题汇总》免费分享_哔哩哔哩_bilibili
2021最新Android程序员中高级进阶学习Framework全套教程(51集大全)/WMS/AMS/Handler/Binder/插件化_哔哩哔哩_bilibili
【Android面试题】2022最新Android中高级大厂高频面试题汇总助力金三银四高薪必备_哔哩哔哩_bilibili