架构组件-ViewModel

234 阅读3分钟

是什么

用来管理界面相关数据。

为什么要引入ViewModel

1、数据可以在配置发生变化后继续存在,横竖屏切换的时候,获取的Model是同一个对象。

2、避免内存泄露:比如进行一个网络请求,需要5秒,然后在1秒的时候关闭页面,此时网络请求回来要处理,否则就会出现内存泄露。

3、onCleared 方法的执行时机:经过测试得出,在横竖屏切换的时候,此时并不会执行onCleared方法,而是会在Activity销毁的时候执行。

如何使用

利用ViewModel 实现获取数据

第一步:写一个实体类

package com.example.myapplication

class User(public val name: String,public val age: String) {
}

第二步:创建ViewModel,内部主要封装了数据的容器和网络请求的操作。

package com.example.myapplication

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {

    // 数据管理
    private val users: MutableLiveData<List<User>> by lazy {
        // 获取数据
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        Thread {
            // 模拟网络请求
            val list = mutableListOf<User>()
            list.add(User("张三", "20"))
            // 将数据存储到容器中
            users.postValue(list)
        }.start()
    }

}

第三步:然后在Activity中监听ViewModel,然后获取数据

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 获取到ViewModel,然后监听数据,然后展示数据
        val model: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
       Log.e("cdx", "model:$model")        model.getUsers().observe(this, Observer {
            Log.e("cdx", "数据个数:" + it.size)
        })

    }
}

在Fragment中使用ViewModel

在Fragment中使用ViewModel 和在Activity中略有不同,传入的参数为 requireActivity()

        val model: TestViewModel = ViewModelProvider(this)[TestViewModel::class.java]
        model.getValue().observe(requireActivity(), Observer {

        })

问题:Cannot invoke setValue on a background thread

原因:在线程中调用.value的属性的形式赋值,就会出现问题。

解决办法:在线程中调用postValue可以,但是代用.value属性的形式不行。

DataBinding 和 ViewModel 的配合使用

第一步:在app-build.gradle-android 下增加

    

dataBinding {
        enabled true
    }

第二步:数据实体类

package com.example.myapplication

class User(var name: String, var age: String) {
}

第三步:定义 Activity 的布局的页面逻辑

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <!--绑定数据-->
        <variable
            name="user"
            type="com.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.age}" />
    </LinearLayout>
</layout>

第四步:定义ViewModel,内部模拟了设置多次数据的逻辑。

package com.example.myapplication

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel


class MyViewModel : ViewModel() {

    // 数据管理
    private val users: MutableLiveData<String> by lazy {
        // 获取数据
        MutableLiveData<String>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<String> {
        return users
    }

    private fun loadUsers() {
        Thread {
            users.postValue("第一次设置数据")
            Thread.sleep(2000)
            users.postValue("第二次设置数据")
        }.start()
    }

    fun setValue(s: String) {
        Thread {
            users.postValue(s)
        }.start()
    }
}

第五步:定义MainActivity的逻辑:从ViewModel中获取数据,然后展示在页面上。

package com.example.myapplication

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 构造DataBinding
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        // 利用ViewModel监听数据
        val user = User("", "")
        //获取到ViewModel,然后监听数据,然后展示数据
        val model: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
        model.getUsers().observe(this, Observer {
            user.name = it
            user.age = it
            binding.user = user
        })
        Thread {
            Thread.sleep(4000)
            model.setValue("第三次设置数据")
        }.start()
    }
}

左右两个Fragment之间通过ViewModel进行交互

效果图

第一步:定义 fragment_left 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="点击"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

第二步:定义TestViewModel 

package com.example.myapplication

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class TestViewModel : ViewModel() {

    // 数据容器
    private val value = MutableLiveData<String>()

    fun getValue(): LiveData<String> {
        return value
    }

    fun setValue(temp: String) {
        value.value = temp
    }

}

第三步:定义LeftFragment 

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.FragmentLeftBinding

class LeftFragment : Fragment() {

    private lateinit var binding: FragmentLeftBinding;

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        binding = DataBindingUtil.inflate<FragmentLeftBinding>(
            inflater,
            R.layout.fragment_left,
            container,
            false
        )
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]
        Log.e("cdxcdx",model.toString())
        // 更新数据
        binding.btn.setOnClickListener {
            model.setValue("1")
        }
    }

}

第四步:定义lefragment_right

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <!--绑定数据-->
        <variable
            name="data"
            type="com.example.myapplication.TestActivityData" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.index}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

第五步:定义RightFragment

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.FragmentLeftBinding
import com.example.myapplication.databinding.FragmentRightBinding

class RightFragment : Fragment() {

    private lateinit var binding: FragmentRightBinding
    val data = TestActivityData()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate<FragmentRightBinding>(
            inflater,
            R.layout.fragment_right,
            container,
            false
        )

        binding.data = data
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 观察数据变化,然后进行展示
        val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]
        Log.e("cdxcdx",model.toString())
        model.getValue().observe(requireActivity(), Observer {
            data.index = it
            binding.data = data
        })
    }

}

第六步:定义Activity中的逻辑

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.myapplication.databinding.ActivityTest1Binding

class Test1Activity : AppCompatActivity() {

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


        val binding =
            DataBindingUtil.setContentView<ActivityTest1Binding>(this, R.layout.activity_test1)

        supportFragmentManager
            .beginTransaction()
            .replace(R.id.contain1, LeftFragment())
            .replace(R.id.contain2, RightFragment())
            .commit()
    }

}

注意事项:

LeftFragment 和 RightFragment 中无法通过ViewMoel进行通信,可能的原因是

LeftModel 中传入的参数为 requireActivity

 val model: TestViewModel = ViewModelProvider(requireActivity())[TestViewModel::class.java]

但是在RightModel 中传入的参数为this

两个Fragment 中生成的ViewModel 不是同一个对象,所以才会导致无法通信。