网上关于怎么用MVVM搭建Android项目的文章已经非常普遍了,很多都是有深度有内涵的长篇或连续剧文章。我的目的不是要效仿那些前辈高人那种写学术性的文章,而是想用一种简单快速或者说不靠谱的方式来阐述下Android的MVVM是怎么回事。
首先咱们先BB几句,模式本身没有好坏之分,Android开发一直是按MVC模式设计,包括Java Swing、JavaFX、JavaEE的Spring都是采用的是MVC模式。只是说在Android中Activity和Fragment即是V层又是C层,业务逻辑和视图表现混合在一起,耦合度很高(我也看到有说XML布局才是V层的说法)。
所以为了解耦才有了MVP模式,MVP也流行过一段日子,把业务逻辑单独放到P层,然后通过接口和V层M层操作,V层和M层无法相互操作。这模式的缺点就是项目写起来太费劲,文件特别多,尤其是接口文件一堆一堆的(曾经接盘一个别人写的项目,刚开始那哥么挻认真的用MVP,但怎奈业务不断的迭代,后期改为MVC了,再后来这锅就砸我手上了……)。
后来就有了MVVM模式,但那句话怎么说来着:一千个人心中有一千个哈姆雷特。所以一千个人写MVVM就有一千个MVVM。正当MVVM也要走上MVP的结局时;谷歌出手了,在2017年时干了两件事,一是扶正了Kotlin成为首席语言,二是推出了一套Android Architecture Components,以后简称AAC。AAC是用来干什么的?简单的说:就是为规范MVVM的写法,免得你们这帮开发者八仙过海各显神通,写出五花八门的MVVM。(话说回来,如果你是学术派的话最好还是自己去看看官网上关于AAC的定义)
现在就说一下Android的MVVM要怎么搭建;先简单说一个MVVM:
- M-Model层:数据模式层,就是项目里定义的各种实体类JavaBean。
- V-View层:视图控件层,就是Activity、Fragment和各种定义的View控件。
- VM-ViewModel层:视图和数据的交互层,就是在这里写业务逻辑的地方,通俗的讲就是在这里调接口、读写文件然后刷新数据。
那么AAC又是怎么规范我们的MVVM的?这要从AAC的组件说起:
- Lifecycles:生命周期管理组件。主要是可以订阅Activity或Fragment的各种生命周期。
- LiveData:基于观察者模式的数据容器。容器的目的是为了将数据和界面的生命周期进行绑定,同时以观察者模式对数据进行订阅,当数据发现更改时可以立即刷新界面。
- ViewModel:让Activity和LiveData产生关联的组件。我们在这个组件里进行各种业务逻辑的处理。组件是直接绑定在Activity上的(暂且这样理解,其实是绑定在一个隐藏的Fragment上的),并将这个Activity作为作用域,并在作用域内以单例形式存在,这样就方便在这个Activity的多个Fragment或控件之间共享数据。
多说无益,这里还是结合实例来说明,继续拿天气预报的项目来演示。
先引入些必要的东西:
// Anko
implementation "org.jetbrains.anko:anko:0.10.8"
implementation "org.jetbrains.anko:anko-commons:0.10.8"
// gson解析
implementation 'com.google.code.gson:gson:2.8.5'
// 协程
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
// AAC
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
implementation group: 'androidx.lifecycle', name: 'lifecycle-viewmodel-ktx', version: '2.2.0-rc03'
// 多用途adapter
implementation 'com.github.mcxtzhang:all-base-adapter:V1.8.0'
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
至于布局界面我这里就不贴出来了,直接放张截图,你们应该一看就明白。

界面分上下两部分,上部放三个RecyclerView列表,分别显示省、市、区,然后根据选择的省市区在下方显示对应的当地天气情况,就这么简单。
现在进入重点环节,我们要自定义一个名为WeatherModel的类,用来处理业务逻辑,这个类继承自ViewModel。ViewModel是一个抽象类,定义如下 :
public abstract class ViewModel {
...
}
我们定义的WeatherModel类如下:
class WeatherModel : ViewModel() {
}
由于ViewModel内没有用abstract修饰的方法名,所以我们定义出来的类也空的,以后再把我们需要的业务逻辑填写进去。现在再来说下这个WeatherModel要怎么实例化。
千万不要不直接new出一个对象,正规的操作应该是这样的:
val model = ViewModelProviders.of(this).get(WeatherModel::class.java)
ViewModelProviders其实是一个工厂模式,用于生产一个继承自ViewModel类的实例。在这里我们用它来生产一个WeatherModel的实例。
- of()方法需要传一个Activity或Fragment的实例。
- get()方法用来指定要生产的ViewModel的子类实例。
这里要注意的是ViewModelProviders只能运行在主线程上,且生产出来的ViewModel实例在整个Activity的作用域内保持单例(Fragment是依附于Activity的),也就说我们在同一个Activity内多次调用ViewModelProviders的get方法返回的始终是同一个实例。
好了,现在我们来讲一下LiveData,LiveData也是一个抽象类,但一般我们不直接用这个类,而是用它的子类MutableLiveData。可以直接new出一个实例使用。例如这样:
var errorMessage = MutableLiveData<String>()
构造器里的泛型指的就是我们需要放到LiveData里的数据类型,毕竟LiveData就是一个装数据的容器。我们可以通过setValue或postValue把数据(或对象实例)放进去。setValue只能在主线程调用,postValue即可以在主线程也可以子线程调用。
前文本说过LiveData是基于观察者模式的,也就是说我们可以对LiveData里数据的进行订阅,然后当我们用setValue或postValue修改容器里的值时就会被观察到,这里我们对之前的errorMessage容器订阅,如果容器里的值被修改的话就在弹一个toast:
errorMessage.observe(this, Observer {
toast(it)
})
熟悉RxJava的应该对这种代码非常亲切吧!这里我们看一下observe方法的定义:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
从定义我们可以看出observe方法必须运行在主线程上,需要传入一个生命周期管理组件和一个类型为Observer的接口实例。Observer定义如下:
public interface Observer<T> {
void onChanged(@Nullable T t);
}
当容器里的值变更后onChanged方法就会被触发。我们前的toast就是写在onChanged方法内部的,只是这个方法名被Kotlin给简化掉了。
到此你应该知道LiveData是个什么东西了吧,那现在有个问题,我们应该把LiveData类的实例写在哪里?还记得我们的WeatherModel类还空着吗?典型的作法就是把LiveData类定义成ViewModel的类属性,然后在ViewModel中执行业务操作,把得到的值放到LiveData的实例当中,而这在此之前就已经在视图层Activity或Fragment里已经订阅了LiveData的事件,当值变动后就能刷新到界面上了。
现在就把完成后的WeatherModel放出来
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.Exception
//接口地址
private const val CITY_URL = "http://v.juhe.cn/weather/citys"
private const val INDEX_URL = "http://v.juhe.cn/weather/index"
private const val KEY = "3bc829216bb4ede1e846fe91b3df5543"
class WeatherModel : ViewModel() {
private var showLoading = MutableLiveData<Boolean>() // 是否显示loading
private var errorMessage = MutableLiveData<String>() // 出错时的错误信息
private var listProvince = MutableLiveData<List<String>>() // 省列表
private var listCity = MutableLiveData<List<String>>() // 市列表
private var listDistrict = MutableLiveData<List<String>>() // 区列表
private var detail = MutableLiveData<String>() // 城市天气详情
fun isShowLoading() = showLoading
fun getErrorMessage() = errorMessage
fun getProvinceList() = listProvince
fun getCityList() = listCity
fun getDistrictList() = listDistrict
fun getDetail() = detail
private var listResult = mutableListOf<CityBean.ResultBean>()
private var selectedProvince: String? = null
private var selectedCity: String? = null
private var selectedDistrict: String? = null
/**
* 调接口获取全部的省市区列表
*/
fun requestCityList() = viewModelScope.launch {
showLoading.value = true
var response: String? = null
// 在子线程上执行网络操作
withContext(Dispatchers.IO) {
response = try {
SimpleHttpUtils.get(CITY_URL, mapOf("key" to KEY))
} catch (e: Exception) {
e.printStackTrace()
//在子线程上给LiveData赋值必须用这个方法,否则出错
errorMessage.postValue(e.message)
null
}
}
showLoading.value = false
if (!response.isNullOrEmpty()) {
Log.i("123", response)
parseResponse(response!!) // 对接口的返回值进行解析
}
}
/**
* 解析接口的反馈值
*/
private fun parseResponse(response: String) {
val result = CityBean.objectFromData(response)
if (result.resultcode == "200") {
listResult = result.result
getAllProvince()
} else {
errorMessage.value = result.reason
}
}
private fun getAllProvince() {
val provinces = listResult.groupBy { it.province }.keys.toList()
listProvince.value = provinces
}
fun setProvince(province: String) {
selectedProvince = province
getCitysByProvince(province)
}
fun getProvince() = selectedProvince
private fun getCitysByProvince(province: String) {
val citys = listResult.filter { it.province == province }
.distinctBy { it.city }
.map { it.city }
.toList()
listCity.value = citys
}
fun getCity() = selectedCity
fun setCity(city: String) {
selectedCity = city
getDistrictByCity(city)
}
private fun getDistrictByCity(city: String) {
val districts = listResult.filter { it.city == city }
.map { it.district }
.toList()
listDistrict.value = districts
}
fun getDistrict() = selectedDistrict
fun setDistrict(district: String) {
selectedDistrict = district
requestDetail()
}
/**
* 调接口获取城市区域的天气情况
*/
private fun requestDetail() = viewModelScope.launch {
showLoading.value = true
val response = try {
withContext(Dispatchers.IO) {
SimpleHttpUtils.get(
INDEX_URL, mapOf(
"format" to "1",
"cityname" to selectedDistrict!!,
"key" to KEY
)
)
}
} catch (e: Exception) {
e.printStackTrace()
errorMessage.postValue(e.message)
null
}
showLoading.value = false
if (!response.isNullOrEmpty()) {
Log.i("123", response)
updateDetail(response)
}
}
private fun updateDetail(response: String) {
val bean = WeatherBean.objectFromData(response).result
val result = "${bean.today.city} ${bean.today.date_y} ${bean.today.week} " +
"\n今日温度:${bean.today.temperature}" +
"\n今日天气:${bean.today.weather}\n" +
"\n最新实况${bean.sk.time}实时发布" +
"\n温度:${bean.sk.temp} 湿度:${bean.sk.humidity}" +
"\n风向:${bean.sk.wind_direction} 风力:${bean.sk.wind_strength}"
detail.value = result
}
}
然后是MainActivity的内容:
private lateinit var model: WeatherModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
。。。
initModel()
}
private fun initModel() {
// 获取ViewModel的实例
model = ViewModelProviders.of(this).get(WeatherModel::class.java)
// 订阅ViewModel中的LiveData的变更事件
model.let { vm ->
vm.isShowLoading().observe(this, Observer {
if (it) {
if (dlg == null || !dlg!!.isShowing)
dlg = indeterminateProgressDialog("Loading...")
} else { dlg?.dismiss() }
})
vm.getErrorMessage().observe(this, Observer { toast(it) })
vm.getProvinceList().observe(this, Observer { updateProvince(it) })
vm.getCityList().observe(this, Observer { updateCity(it) })
vm.getDistrictList().observe(this, Observer { updateDistrict(it) })
vm.getDetail().observe(this, Observer { tvDetail.text = it })
}
}
override fun onStart() {
super.onStart()
model.requestCityList() // 通过ViewModel调接口
}
MainActivity持有ViewModel的实例,并通过实例对ViewModel内的LiveData实例进行订阅,当业务逻辑更改了LiveData内的值后就能很方便的刷新界面了,从而实现了业务和视图的解耦。
最后放一下这个实例的源代码下载:
MVVMDemo.rar: https://t00y.com/file/22686471-412632153
点击链接加入群聊【口袋里的安卓】:https://jq.qq.com/?_wv=1027&k=5z4fzdT
或关注微信公众号:
网友评论