ViewModel原理解析

ViewModel

ViewModels负责为View准备数据。它们将数据公开给正在侦听更改的任何视图
ViewModel 的作用是专门存放与界面相关的数据,分担 Activity/Fragment 的逻辑,同时会维护自己独立的生命周期。如当系统配置发生变更(如切换语言等)、横竖屏切换等,可能会导致 Activity 销毁重建,假设要被销毁是 Activity A,需要被重新创建的是 Activity B,虽然他们都属于同一类型,但是是两个不同的实例对象。因此在 Activity 销毁重建的过程中,就涉及 A 在销毁时,其内部维护的数据要过渡到重建的 B 中,这就依赖于 ViewModel。

  • ViewModel可以在Activity配置更改中保留其状态。它保存的数据可立即供下一个Activity实例使用,无需在onSaveInstanceState()中保存数据并手动恢复。
  • ViewModel比特定的Activity或Fragment实例更长
  • ViewModel允许在Fragments之间轻松共享数据(这意味着您不再需要通过活动协调操作)。
  • ViewModel将保留在内存中,直到它的作用域生命周期永久消失.
  • 由于ViewModel比Activity或Fragment实例更长,因此它不应直接引用其中的任何Views或保持对上下文的引用。这可能会导致内存泄漏(https://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/)。
  • 如果ViewModel需要Application上下文(例如,查找系统服务),它可以继承AndroidViewModel类并具有在构造函数中接收Application的构造函数。

由于 ViewModel 的生命周期是由系统维护的,因此不能直接在代码中通过 new 的方式创建。

======创建 AndroidViewModel=======
 viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)

----AndroidViewModel也是继承viewModel,只是多了一层环境的封装----
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }
}

Databinding+LiveData+ViewModel 实现拨号小案例

777.jpg

功能说明

1.数字按钮点击动态显示到UI
2.del按钮和back按钮 对UI页面数据进行删除处理
3.call按钮,调用拨号操作

源码

1.MainViewModel.class 用于数据处理和页面绑定


/**
 * @author 付影影
 * @desc
 * @date 2022/6/10
 */
class MainViewModel(application: Application) : AndroidViewModel(application) {
     val phoneInfo:MutableLiveData<String> by lazy { MutableLiveData<String>() }
   init {
       phoneInfo.value = ""
   }

    @SuppressLint("StaticFieldLeak")
    val context:Context = application

    //拨号
    fun appendNumber(number:String){
        phoneInfo.value = phoneInfo.value+number
    }

    //删除数据
    fun backSpaceNumber(){
        val length:Int = phoneInfo.value?.length?:0
        if (length > 0){
            phoneInfo.value = phoneInfo.value?.substring(0,phoneInfo.value!!.length-1)
        }
    }

    //清除数据
    fun clear(){
        phoneInfo.value = ""
    }

    //打电话
    fun callPhone(){
        val intent = Intent()
        intent.action = Intent.ACTION_CALL
        intent.data = Uri.parse("tel:"+phoneInfo.value)
        //非activity环境 启动拨号或者跳转页面,都需要配置flags,否则会崩溃、在activity环境默认启动方式
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        context.startActivity(intent)
    }


}

2.xml文件绑定 databinding

<?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">

    <data>
        <variable
            name="vm"
            type="com.shadow.testapplication.MainViewModel" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:layout_gravity="center"
            android:textSize="20sp"
            android:layout_marginTop="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{vm.phoneInfo}" />

      <LinearLayout
          android:layout_marginTop="20dp"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">
          <Button
              android:layout_width="0dp"
              android:layout_weight="1"
              android:text="1"
              android:onClick="@{()->vm.appendNumber(String.valueOf(1))}"
              android:layout_height="50dp" />
          <Button
              android:layout_width="0dp"
              android:layout_weight="1"
              android:text="2"
              android:onClick="@{()->vm.appendNumber(String.valueOf(2))}"
              android:layout_height="50dp" />
          <Button
              android:layout_width="0dp"
              android:layout_weight="1"
              android:text="3"
              android:onClick="@{()->vm.appendNumber(String.valueOf(3))}"
              android:layout_height="50dp" />
      </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="4"
                android:onClick="@{()->vm.appendNumber(String.valueOf(4))}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="5"
                android:onClick="@{()->vm.appendNumber(String.valueOf(5))}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="6"
                android:onClick="@{()->vm.appendNumber(String.valueOf(6))}"
                android:layout_height="50dp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="7"
                android:onClick="@{()->vm.appendNumber(String.valueOf(7))}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="8"
                android:onClick="@{()->vm.appendNumber(String.valueOf(8))}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="9"
                android:onClick="@{()->vm.appendNumber(String.valueOf(9))}"
                android:layout_height="50dp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="*"
                android:onClick="@{()->vm.appendNumber(@string/btn_1)}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="0"
                android:onClick="@{()->vm.appendNumber(String.valueOf(0))}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="#"

                android:onClick="@{()->vm.appendNumber(@string/btn_2)}"
                android:layout_height="50dp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="del"
                android:onClick="@{()->vm.clear()}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="call"
                android:onClick="@{()->vm.callPhone()}"
                android:layout_height="50dp" />
            <Button
                android:layout_width="0dp"
                android:layout_weight="1"
                android:text="back"
                android:onClick="@{()->vm.backSpaceNumber()}"
                android:layout_height="50dp" />
        </LinearLayout>

    </LinearLayout>
</layout>

3.MainActivity.class 声明databinding,绑定viewmodel和lifecycle

package com.shadow.testapplication

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.shadow.testapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding:ActivityMainBinding
    lateinit var viewModel:MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        binding =  DataBindingUtil.setContentView(this,R.layout.activity_main)
        viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)

        binding.vm = viewModel
        binding.lifecycleOwner = this
    }

    override fun onRetainCustomNonConfigurationInstance(): Any? {
        Log.d("hh","横竖屏切换")
        return super.onRetainCustomNonConfigurationInstance()
    }
}

源码解析

 viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(MainViewModel::class.java)
  1. ViewModelProvider 第一个参数传入的是this,MainActivity,
    ComponentActivity实现了ViewModelStoreOwner接口,实现方法ViewModelStore getViewModelStore();,
    888.PNG

    getViewModelStore() 实现 主要通过 NonConfigurationInstances nc 对viewmoStore进行缓存。
    333.PNG

ViewModelStore 主要是通过HashMap对viewmodel进行处理增加,删除

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

2.ViewModelProvider 第二参数传入的是Factory,采用依赖倒置原则,面对接口开发。
NewFactory,AndroidNewFactory,通过反射 实例化viewmodel

111.png
  1. get 方法,把viewModel 加入viewModelStore
   public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

为什么横竖屏切换,viewmodel能保证数据稳定性

在屏幕横竖屏转换的过程,通过nc 恢复已缓存viewmodelStore,得到viewmodel,自然能保证数据的稳定性


444.PNG

为什么viewmodel的生命周期那么长

ViewModel能够将数据从Activity中剥离出来。只要Activity不被销毁,ViewModel会一直存在,并且独立于Activity的配置变化,即旋转屏幕导致的Activity重建,不会影响到ViewModel

因为viewmodel只有组件激活Destoryed事件时,才会romove所有的viewmodel


999.png
555.PNG

ViewModel+LiveData实现Fragment间通信

Fragment可以看作是Activity的子页面,即,一个Activity中可以包含多个Fragment,这些Fragment彼此独立,但是又都属于同一个Activity。

public class ShareDataViewModel extends ViewModel
{
    private MutableLiveData<Integer> progress;

    public LiveData<Integer> getProgress()
    {
        if (progress == null)
        {
            progress = new MutableLiveData<>();
        }
        return progress;
    }

    @Override
    protected void onCleared()
    {
        super.onCleared();
        progress = null;
    }
}
=================fragment 中获取viewmodel和livedata实例==============
然后订阅livedata,动态修改数据,通过liveData setValue 更新数据,两个fragment都这样操作,就可以实现数据同步更新。
        //注意:这里ViewModelProviders.of(getActivity())这里的参数需要是Activity,而不能是Fragment,否则收不到监听  (老版本如此写)
        final ShareDataViewModel shareDataViewModel = ViewModelProviders.of(getActivity()).get(ShareDataViewModel.class);
        final MutableLiveData<Integer> liveData = (MutableLiveData<Integer>)shareDataViewModel.getProgress();

 //通过observe方法观察ViewModel中字段数据的变化,并在变化时,得到通知
        liveData.observe(this, new Observer<Integer>()
        {
            @Override
            public void onChanged(@Nullable Integer progress)
            {
              //监听数据变化,更新数据
                seekBar.setProgress(progress);
            }
        });
      //通过seekBar测试数据更新
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
        {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
            {
                //用户操作SeekBar时,更新ViewModel中的数据
                liveData.setValue(progress);
            }
        });
     
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容