美文网首页
ViewModel原理解析

ViewModel原理解析

作者: 付小影子 | 来源:发表于2022-06-10 12:27 被阅读0次

    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);
                }
            });
         
    

    相关文章

      网友评论

          本文标题:ViewModel原理解析

          本文链接:https://www.haomeiwen.com/subject/xnxbmrtx.html