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 by lazy { MutableLiveData() }
   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





    
        

    

    

        

      
          

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 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 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 get(@NonNull String key, @NonNull Class 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 progress;

    public LiveData 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 liveData = (MutableLiveData)shareDataViewModel.getProgress();

 //通过observe方法观察ViewModel中字段数据的变化,并在变化时,得到通知
        liveData.observe(this, new Observer()
        {
            @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);
            }
        });
     

你可能感兴趣的:(ViewModel原理解析)