MVVM架构,将整个应用分为三层,View层,VM层,Model层。其中View层单向引用VM层,VM层单向引用Model层。如上图。
单向引用,而非双向引用,这是MVVM与MVP最大的区别。View层,只是单向引用VM层,VM层不需要引用View层,但是却可以
更新View层。这是通过VM层的观察者模式实现的,在这里使用架构组件LiveData,观察者注册LiveData,当LiveData数据发生变更
的时候,就会通知注册的观察者。
VM层,执行业务逻辑,获取Model层的数据,Model层的数据由repository来提供。
 
举例子:
ChooseAreaFragment是View层,它持有ViewModel,它可以监听相关数据,相关数据发生变化的时候,对应的UI就会被更新。
比如:dataChanged数据发生变化,就会执行定义的观察者操作。
  viewModel.dataChanged.observe(this, Observer {
            adapter.notifyDataSetChanged()
            listView.setSelection(0)
            closeProgressDialog()
        })
  1. class ChooseAreaFragment : Fragment() {
  2.  
  3.     private val viewModel by lazy { ViewModelProviders.of(this, InjectorUtil.getChooseAreaModelFactory()).get(ChooseAreaViewModel::class.java) }
  4.     private var progressDialog: ProgressDialog? = null
  5.     private lateinit var adapter: ArrayAdapter<String>
  6.  
  7.     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
  8.         val view = inflater.inflate(R.layout.choose_area, container, false)
  9.         val binding = DataBindingUtil.bind<ChooseAreaBindingImpl>(view)
  10.         binding?.viewModel = viewModel
  11.         return view
  12.     }
  13.  
  14.     override fun onActivityCreated(savedInstanceState: Bundle?) {
  15.         super.onActivityCreated(savedInstanceState)
  16.         adapter = ChooseAreaAdapter(context!!, R.layout.simple_item, viewModel.dataList)
  17.         listView.adapter = adapter
  18.         observe()
  19.     }
  20.  
  21.     private fun observe() {
  22.         viewModel.currentLevel.observe(this, Observer { level ->
  23.             when (level) {
  24.                 LEVEL_PROVINCE -> {
  25.                     titleText.text = "中国"
  26.                     backButton.visibility = View.GONE
  27.                 }
  28.                 LEVEL_CITY -> {
  29.                     titleText.text = viewModel.selectedProvince?.provinceName
  30.                     backButton.visibility = View.VISIBLE
  31.                 }
  32.                 LEVEL_COUNTY -> {
  33.                     titleText.text = viewModel.selectedCity?.cityName
  34.                     backButton.visibility = View.VISIBLE
  35.                 }
  36.             }
  37.         })
  38.  
  39.         viewModel.dataChanged.observe(this, Observer {
  40.             adapter.notifyDataSetChanged()
  41.             listView.setSelection(0)
  42.             closeProgressDialog()
  43.         })
  44.         viewModel.isLoading.observe(this, Observer { isLoading ->
  45.             if (isLoading) showProgressDialog()
  46.             else closeProgressDialog()
  47.         })
  48.         viewModel.areaSelected.observe(this, Observer { selected ->
  49.             if (selected && viewModel.selectedCounty != null) {
  50.                 if (activity is MainActivity) {
  51.                     val intent = Intent(activity, WeatherActivity::class.java)
  52.                     intent.putExtra("weather_id", viewModel.selectedCounty!!.weatherId)
  53.                     startActivity(intent)
  54.                     activity?.finish()
  55.                 } else if (activity is WeatherActivity) {
  56.                     val weatherActivity = activity as WeatherActivity
  57.                     weatherActivity.drawerLayout.closeDrawers()
  58.                     weatherActivity.viewModel.weatherId = viewModel.selectedCounty!!.weatherId
  59.                     weatherActivity.viewModel.refreshWeather()
  60.                 }
  61.                 viewModel.areaSelected.value = false
  62.             }
  63.         })
  64.         if (viewModel.dataList.isEmpty()) {
  65.             viewModel.getProvinces()
  66.         }
  67.     }
  68.  
  69.     /**
  70.      * 显示进度对话框
  71.      */
  72.     private fun showProgressDialog() {
  73.         if (progressDialog == null) {
  74.             progressDialog = ProgressDialog(activity)
  75.             progressDialog?.setMessage("正在加载...")
  76.             progressDialog?.setCanceledOnTouchOutside(false)
  77.         }
  78.         progressDialog?.show()
  79.     }
  80.  
  81.     /**
  82.      * 关闭进度对话框
  83.      */
  84.     private fun closeProgressDialog() {
  85.         progressDialog?.dismiss()
  86.     }
  87.  
  88.     companion object {
  89.         const val LEVEL_PROVINCE = 0
  90.         const val LEVEL_CITY = 1
  91.         const val LEVEL_COUNTY = 2
  92.     }
  93.  
  94. }
VM层,ViewModel:
使用LiveData包装被View层监听的数据,在VM层数据发生的变化,会通知到View层,但却无需要View层的引用。
因为LiveData应用了观察者模式,注册的观察者,在数据发生变化的时候,会自动通知观察者。
如下,currentLevel,dataChanged,isLoading等都是使用LiveData包装的,意味着,它们发生变化的时候View层会监听得到,从而进行相应的更新操作。
在VM层,持有Model层的引用,Model层的数据获取,网络请求,都依赖repository实现。
  1. class ChooseAreaViewModel(private val repository: PlaceRepository) : ViewModel() {
  2.  
  3.     var currentLevel = MutableLiveData<Int>()
  4.  
  5.     var dataChanged = MutableLiveData<Int>()
  6.  
  7.     var isLoading = MutableLiveData<Boolean>()
  8.  
  9.     var areaSelected = MutableLiveData<Boolean>()
  10.  
  11.     var selectedProvince: Province? = null
  12.  
  13.     var selectedCity: City? = null
  14.  
  15.     var selectedCounty: County? = null
  16.  
  17.     lateinit var provinces: MutableList<Province>
  18.  
  19.     lateinit var cities: MutableList<City>
  20.  
  21.     lateinit var counties: MutableList<County>
  22.  
  23.     val dataList = ArrayList<String>()
  24.  
  25.     fun getProvinces() {
  26.         currentLevel.value = LEVEL_PROVINCE
  27.         launch {
  28.             provinces = repository.getProvinceList()
  29.             dataList.addAll(provinces.map { it.provinceName })
  30.         }
  31.     }
  32.  
  33.     private fun getCities() = selectedProvince?.let {
  34.         currentLevel.value = LEVEL_CITY
  35.         launch {
  36.             cities = repository.getCityList(it.provinceCode)
  37.             dataList.addAll(cities.map { it.cityName })
  38.         }
  39.     }
  40.  
  41.     private fun getCounties() = selectedCity?.let {
  42.         currentLevel.value = LEVEL_COUNTY
  43.         launch {
  44.             counties = repository.getCountyList(it.provinceId, it.cityCode)
  45.             dataList.addAll(counties.map { it.countyName })
  46.         }
  47.     }
  48.  
  49.     fun onListViewItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
  50.         when {
  51.             currentLevel.value == LEVEL_PROVINCE -> {
  52.                 selectedProvince = provinces[position]
  53.                 getCities()
  54.             }
  55.             currentLevel.value == LEVEL_CITY -> {
  56.                 selectedCity = cities[position]
  57.                 getCounties()
  58.             }
  59.             currentLevel.value == LEVEL_COUNTY -> {
  60.                 selectedCounty = counties[position]
  61.                 areaSelected.value = true
  62.             }
  63.         }
  64.     }
  65.  
  66.     fun onBack() {
  67.         if (currentLevel.value == LEVEL_COUNTY) {
  68.             getCities()
  69.         } else if (currentLevel.value == LEVEL_CITY) {
  70.             getProvinces()
  71.         }
  72.     }
  73.  
  74.     private fun launch(block: suspend () -> Unit) = viewModelScope.launch {
  75.         try {
  76.             isLoading.value = true
  77.             dataList.clear()
  78.             block()
  79.             dataChanged.value = dataChanged.value?.plus(1)
  80.             isLoading.value = false
  81.         } catch (t: Throwable) {
  82.             t.printStackTrace()
  83.             Toast.makeText(CoolWeatherApplication.context, t.message, Toast.LENGTH_SHORT).show()
  84.             dataChanged.value = dataChanged.value?.plus(1)
  85.             isLoading.value = false
  86.         }
  87.     }
  88.  
  89. }
Model层:
在这个例子中,Model层对外提供的方法是
getProvinceList,getCityList,getCountyList。
它的数据来源,可能是数据库Dao,或者是网络,各自的实现,再委托到具体的方法去实现。
  1. class PlaceRepository private constructor(private val placeDao: PlaceDao, private val network: CoolWeatherNetwork) {
  2.  
  3.     suspend fun getProvinceList() = withContext(Dispatchers.IO) {
  4.         var list = placeDao.getProvinceList()
  5.         if (list.isEmpty()) {
  6.             list = network.fetchProvinceList()
  7.             placeDao.saveProvinceList(list)
  8.         }
  9.         list
  10.     }
  11.  
  12.     suspend fun getCityList(provinceId: Int) = withContext(Dispatchers.IO) {
  13.         var list = placeDao.getCityList(provinceId)
  14.         if (list.isEmpty()) {
  15.             list = network.fetchCityList(provinceId)
  16.             list.forEach { it.provinceId = provinceId }
  17.             placeDao.saveCityList(list)
  18.         }
  19.         list
  20.     }
  21.  
  22.     suspend fun getCountyList(provinceId: Int, cityId: Int) = withContext(Dispatchers.IO) {
  23.         var list = placeDao.getCountyList(cityId)
  24.         if (list.isEmpty()) {
  25.             list = network.fetchCountyList(provinceId, cityId)
  26.             list.forEach { it.cityId = cityId }
  27.             placeDao.saveCountyList(list)
  28.         }
  29.         list
  30.     }
  31.  
  32.     companion object {
  33.  
  34.         private var instance: PlaceRepository? = null
  35.  
  36.         fun getInstance(placeDao: PlaceDao, network: CoolWeatherNetwork): PlaceRepository {
  37.             if (instance == null) {
  38.                 synchronized(PlaceRepository::class.java) {
  39.                     if (instance == null) {
  40.                         instance = PlaceRepository(placeDao, network)
  41.                     }
  42.                 }
  43.             }
  44.             return instance!!
  45.         }
  46.  
  47.     }
  48.  
  49. }
以上就是MVVM的实例解析。应用MVVM的时候,关键是划分功能属于哪一个层次,然后,再确定引用关系。划分功能属于哪个层次,可以依据单一职责原则,让功能代码原子化,再在这一基础上去区分层次。
 
 

android mvvm实例解析的更多相关文章

  1. 【转】Android HAL实例解析

    原文网址:http://www.embedu.org/Column/Column339.htm 作者:刘老师,华清远见嵌入式学院讲师. 一.概述 本文希望通过分析台湾的Jollen的mokoid 工程 ...

  2. Android HAL实例解析

    一.概述 本文希望通过分析台湾的Jollen的mokoid 工程代码,和在s5pc100平台上实现过程种遇到的问题,解析Andorid HAL的开发方法. 二.HAL介绍 现有HAL架构由Patric ...

  3. Android AIDL实例解析

    AIDL这项技术在我们的开发中一般来说并不是很常用,虽然自己也使用新浪微博的SSO登录,其原理就是使用AIDL,但是自己一直没有动手完整的写过AIDL的例子,所以就有了这篇简单的文章. AIDL(An ...

  4. Android实例-Delphi开发蓝牙官方实例解析(XE10+小米2+小米5)

    相关资料:1.http://blog.csdn.net/laorenshen/article/details/411498032.http://www.cnblogs.com/findumars/p/ ...

  5. Android开发之IPC进程间通信-AIDL介绍及实例解析

    一.IPC进程间通信 IPC是进程间通信方法的统称,Linux IPC包括以下方法,Android的进程间通信主要采用是哪些方法呢? 1. 管道(Pipe)及有名管道(named pipe):管道可用 ...

  6. Android Service完全解析,关于服务你所需知道的一切(上)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的A ...

  7. [转] Android Volley完全解析(一),初识Volley的基本用法

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[-] Volley简介 下载Volley StringRequest的用法 JsonRequest的用法   转载请注明出处:http ...

  8. Android IntentService完全解析 当Service遇到Handler

    一 概述 大家都清楚,在Android的开发中,凡是遇到耗时的操作尽可能的会交给Service去做,比如我们上传多张图,上传的过程用户可能将应用置于后台,然后干别的去了,我们的Activity就很可能 ...

  9. Android Volley完全解析

    1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行H ...

  10. Android Bitmap 全面解析(四)图片处理效果对比 ...

    对比对象: UIL Volley 官方教程中的方法(此系列教程一里介绍的,ImageLoader的处理方法和官方的差不多) -------------------------------------- ...

随机推荐

  1. linux 开机默认进入命令行模式

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  2. 问题--链表指针传参,修改next指针只传值

    1.问题--链表指针传参,修改next指针只传值 Link_creat_head(&head, p_new);//将新节点加入链表 在这当中head头指针传的是地址,而p_new传的是值,这二 ...

  3. MyBatis03——ResultMap和分页相关

    ResultMap和分页相关 当属性名和字段名不一致的时候 解决方法 1.数据库中创建user表 字段 id.name.pwd 2.Java中的实体类 @Data public class User ...

  4. Linux-文件权限-rwx-chmod

  5. 【Kafka系列】(一)Kafka入门

    有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 Kafka是什么? 一句话概括:Apache K ...

  6. [转帖]oracle rac后台进程和LMS说明

    本文摘抄录oracle官方文档,oracle rac使用的后台进程,用以备忘,记录之. About Oracle RAC Background Processes The GCS and GES pr ...

  7. [转帖]设置kafka 数据保留时间

    https://www.cnblogs.com/gao88/p/12539112.html kafka 单独设置某个topic的数据过期时间kafka 默认存放7天的临时数据,如果遇到磁盘空间小,存放 ...

  8. [转帖]Linux—微服务启停shell脚本编写模板

    https://www.jianshu.com/p/1e1080a39dc5 run.sh #!/bin/bash if [ -s java.pid ] then echo "重复启动,退出 ...

  9. [转帖]Nginx性能优化详解

    https://developer.aliyun.com/article/886146?spm=a2c6h.24874632.expert-profile.256.7c46cfe9h5DxWK 感觉文 ...

  10. vue/cli中css.sourceMap-open-inline-host-port-https-openPage-compress -devServer.proxy的简单介绍

    Vue/cli4.0 配置属性--css.sourceMap 设置是否开启 css 的 sourse map功能. css 的 sourse map作用类似与 js 的 sourse map. 注意: ...