Jetpack组件总结系列篇 (一):ViewModel的使用和原理分析

看完本文将收获:

一.ViewModel介绍以及优势
二. ViewModel的使用
三. ViewModel的实现原理
四. 关于ViewModel相关的问题Q-And-A

ViewModel官方介绍

一.ViewModel的介绍以及优势

  • 简介:
    ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
  • 优势:
    ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。

ViewModel 类的主要优势实际上有两个方面:

  • 它允许您持久保留界面状态。(ViewModel的生命周期)
    在这里插入图片描述

  • 它可以提供对业务逻辑的访问权限。

二.ViewModel的使用

详细可以看Google官方发表的使用方式(结合其他jetpack组件)

简单使用:

实现一个点击按钮让数字 + 1的功能

  1. 继承ViewModel
public class MyViewModel extends ViewModel {
    
    

    public int number;

}
  1. 获取实例ViewModelProvider.get(xxx.class)和使用
public class MainActivity extends AppCompatActivity {
    
    

    private TextView textView;
    private MyViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
        textView.setText(String.valueOf(viewModel.number));
        Log.d("jian", "onCreate: " + this + "     " + viewModel);
    }

    public void plusNumber(View view) {
    
    
        textView.setText(String.valueOf(++viewModel.number));
    }
}

在这里插入图片描述
当旋转屏幕时,数据依旧不会丢失,也就是“新创建的activity实例状态跟销毁前状态一样”
在这里插入图片描述
从打印的信息也可知,虽然Activity对象被重建了(跟屏幕旋转前实例不一样),但ViewModel依然没有被销毁重建(跟屏幕旋转前实例一样)

三. ViewModel的实现原理

学习ViewModel的实现原理前需要先掌握 Activity的保存实例状态机制,不懂的可以先阅读一下详解Activity和Fragment生命周期以及保存实例状态机制

思考和探究为什么ViewModel旋转时数据不会丢失

  1. 刚开始使用的时候看到屏幕旋转数据没丢失,viewmodel实例还是一样,直呼简直不要太方便,然后就突然在想,为什么ViewModel这么神奇?
  2. 当看到官方的解说就联想到保存实例状态机制

ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。

  1. 终于在看了相关保存实例状态机制源码的时候,找到了关键代码

onRetainNonConfigurationInstance():

    public final Object onRetainNonConfigurationInstance() {
    
    
        // Maintain backward compatibility.
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
    
    
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
    
    
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
    
    
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

  • 当系统配置状态改变的时候,系统会调用onRetainNonConfigurationInstance()把存储viewmodel的仓库保存下来,即:
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;  
        //custom是我们重写onRetainCustomNonConfigurationInstance()后的返回值,
        //也就是我们想要保存的“上个状态” (数据)
        nci.viewModelStore = viewModelStore; 
        //这里将viewModelStore对象保存
        return nci;
  • 看一下NonConfigurationInstances这个类
    static final class NonConfigurationInstances {
    
    
        Object custom;
        ViewModelStore viewModelStore;
    }

接着看viewmodel对象是如何创建的:

viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);

点进get看逻辑:

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    
    
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
    
    
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

在此处先获取class对象的CanonicalName,然后以 DEFAULT_KEY + “:” + canonicalName为键值key调用下面这个方法

扫描二维码关注公众号,回复: 14844918 查看本文章
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    
    
        ViewModel viewModel = mViewModelStore.get(key);
        //此处调用get(key)获取到viewmodel对象
        if (modelClass.isInstance(viewModel)) {
    
    
             //判断viewModel是不是这个class对象的实例,因为key值可以人为定义,可能造成同一键值获取到不同class的viewmodel实例,(因为我们使用的都是自定义Viewmodel(继承)) 
            if (mFactory instanceof OnRequeryFactory) {
    
    
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
                //onRequery默认空实现,不用去看
                //OnRequeryFactory是要我们去继承和重写onRequery方法的,无需看
            }
            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);
            //利用传进来的viewmodelfactory去创建viewmodel对象
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

ViewModelFactory都是利用反射去创建viewmodel对象的(因为传进来的参数是class对象)

  • Tips: ViewModelProvider.AndroidViewModelFactory可以获取全局的Application
    看构造方法
        public AndroidViewModelFactory(@NonNull Application application) {
    
    
            mApplication = application;
        }

根据上面的代码可以知道,viewmodel是在mViewModelStore中利用key去获取的(猜测内部实现是hashmap)
再看一下ViewModelStore这个类(确实是hashmap)

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

此时又在想,ViewModelStore对象是怎么来的? 接着继续跟踪mViewModelStore对象

 private final ViewModelStore mViewModelStore;
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    
    
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    
    
        mFactory = factory;
        mViewModelStore = store;
    }
  • 跟踪发现mViewModelStore在构造viewmodelprovider对象的时候被赋值,且是final修饰,值对象为owner.getViewModelStore(), ower是ViewModelStoreOwner接口实例。
  • 继续跟踪,发现mViewModelStore是在ComponentActivity.java的ensureViewModelStore()方法内被赋值的, 而owner.getViewModelStore()内部是调用 ensureViewModelStore()方法确保mViewModelStore不为null
    @Override
    public ViewModelStore getViewModelStore() {
    
    
        if (getApplication() == null) {
    
    
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }
    void ensureViewModelStore() {
    
    
        if (mViewModelStore == null) {
    
    
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
    
    
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
    
    
                mViewModelStore = new ViewModelStore();
            }
        }
    }
  • owner关ComponentActivity什么事?
    在这里插入图片描述
    ComponentActivity实现了ViewModelStoreOwner接口,而AppCompatActivity间接继承了CompoentActivity,因此此处的ower其实就是我们的传进来的activity实例对象
    在这里插入图片描述
  • 接着再来看一下ensureViewModelStore()方法的具体实现:
    void ensureViewModelStore() {
    
    
           //当mViewModelStore不为null时,啥事不做
        if (mViewModelStore == null) {
    
    
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            //获取上个activity配置状态改变时调用onRetainNonConfigurationInstance()的返回值,也就是保存下来的custom和mviewstore对象        
            if (nc != null) {
    
    
            //把activity销毁前状态的viewModelStore赋值给重建后的activity的mviewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
    
    
                mViewModelStore = new ViewModelStore();
            }
        }
    }

经过以上分析在总结梳理一下:

  • 屏幕旋转时(系统配置状态改变),系统会调用Activity的onRetainNonConfigurationInstance(),此时会保存activity销毁前的mViewModelStore
  • viewmodel是通过mViewModelStore.get(key)获取的,所以最好不用自定义key,而是用系统生成的默认key值(否则可能会get到不同class对象的viewmodel而抛出异常)
  • mViewModelStore的赋值是在ensureViewModelStore()方法中实现的
  1. 如果当前mViewModelStore != null,不做处理(也就是当前的Acitvity没被销毁)
  2. 通过getLastNonConfigurationInstance()的返回值获取到activity销毁重建前的状态里面的ViewModelStore,并赋值给mViewModelStore (只有配置状态改变时getLastNonConfigurationInstance()的返回值才不为null, 因为配置状态改变时才会调用onRetainNonConfigurationInstance())
  3. 如果1,2都为null,就重新new 一个ViewModelStore (即activity没有被非正常销毁时(旋转屏幕)的创建)

以上就是屏幕旋转时,viewmodel数据不会丢失的分析的原因。

四. 关于ViewModel相关的问题Q-And-A

从当前的activity跳转到另外的activity,再按返回键返回时ViewModel还在吗? 要是使用跳转方式跳转过来呢?

  1. 在返回来viewModel还在 (上面mViewmodelstore赋值的第1点)
    ,因为此时activity不会被销毁重建(onPause->onStop->onStart…)
  2. 使用跳转方式需要分activity启动模式了:
  • 如果跳转回来的activity实例是新创建(没有被非正常销毁),那么获取viewmodelstore也是新创建的(上面mViewmodelstore赋值的第3点),使用viewmodel实例也是新创建的
  • 如果跳转回来的activity实例还是跟原来的一样,那viewmodelstore,viewmodel都跟跳转前的一样,不会重新创建新实例 (上面mViewmodelstore赋值的第1点)

根据上述分析原理如果利用viewmodel实现fragment之间共享数据?

  • 实现fragment之间贡献数据前提是owner对象一定要使用共有的activity
    在Fragment中:
  //owner对象必须传入getActivity()
 MyViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);

写个小案例,利用livedata + viewmodel 实现fragment间数据的共享联动效果
在这里插入图片描述
拖动上面进度条时下面的进度条会跟着动,拖动下面的进度条时上面的进度条会跟着动

  • 核心代码:
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    
    
        View root = inflater.inflate(R.layout.fragment_first, container, false);
        SeekBar seekBar = root.findViewById(R.id.seekBar);
        //只要上下俩个fragment的此处owner传入getActivity()就能实现了
        MyViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
        Log.d("ning", "onCreateView: " + viewModel);
        viewModel.getProgress().observe(getActivity(), new Observer<Integer>() {
    
    
            @Override
            public void onChanged(Integer i) {
    
    
                seekBar.setProgress(i);
            }
        });

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    
    
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    
    
                viewModel.getProgress().setValue(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
    
    

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
    
    

            }
        });
        // Inflate the layout for this fragment
        return root;
    }

在Fragment中屏幕旋转时数据为什么不会丢失?

ViewModel 存储在 FragmentManagerViewModel 中的,而 FragmentManagerViewModel 是存储在宿主 Activity 中的 ViewModelStore 中,又因 Activity 中 ViewModelStore不会因配置改变而销毁,故 Fragment 中 ViewModel 也不会因配置改变而销毁。
在这里插入图片描述
若要看源码请自行看,这个涉及到Activity的生命周期方法的详细逻辑

ViewModel什么时候被清除?

在ComponentActivity.java中可以看到

        getLifecycle().addObserver(new LifecycleEventObserver() {
    
    
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
    
    
                if (event == Lifecycle.Event.ON_DESTROY) {
    
    
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {
    
    
                        getViewModelStore().clear();
                    }
                }
            }
        });

当 event == Lifecycle.Event.ON_DESTROY 和 isChangingConfigurations()为false 时,(即Activity会销毁且不是因为状态改变被销毁时)会调用 getViewModelStore().clear()方法把viewmodelstore给清空掉

    public final void clear() {
    
    
        for (ViewModel vm : mMap.values()) {
    
    
            vm.clear();
        }
        mMap.clear();
    }

因此在调用finish()方法(人为或者系统) 的时候 viewmodelstore数据是会被清空的,即Activity再创建的时候数据不会被保存。

下次不定时更新文章应该是Lifecycle 然后再 Livedata总结,喜欢的可以点个赞加关注哈。

猜你喜欢

转载自blog.csdn.net/XJ200012/article/details/128622414