美文网首页
Jetpack之ViewMode初识,使用和源码讲解

Jetpack之ViewMode初识,使用和源码讲解

作者: Jsonjia | 来源:发表于2022-12-30 11:26 被阅读0次

    今天是2022年最后一天,祝大家元旦快乐。


    image.png

    ViewModel初识

    ViewModelJetpack组件之一,它注重生命周期的方式存储和管理界面的数据,它是让数据在屏幕旋转等配置更改后继续留存。通俗一点就是:手机屏幕发生旋转后,数据依然还在。

    ViewModel没出来之前,怎么存取数据?

    ViewModel出现之前,一般屏幕发生旋转时候,Activity生命周期会重新创建,我们会在onSaveInstance()里面保存数据,然后在onCreate(Bundle saveInstance)里面取数据,但是这个数据有弊端,必须要实现序列化和反序列化,并且这个数据不能太大。

    class ViewModelActivity : AppCompatActivity() {
    
         override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
                   //(2)取数据
         }
    
         override fun onSaveInstanceState(outState: Bundle) {
              super.onSaveInstanceState(outState)
                   //(1)存数据
         }
    }
    

    所以ViewModel就是为了解决这个问题的。

    ViewModel的使用
    (1)添加ViewModel依赖

    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    

    (2)定义一个类继承ViewModel(AndroidViewModel)即可

    class MyViewModel : ViewModel() {
          val updateLiveData by lazy { MutableLiveData<String>() }
    }
    

    (3)Activity创建ViewModel实例

    class ViewModelActivity : AppCompatActivity() {
    
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
    
              var myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
              myViewModel.updateLiveData.observe(this) {
               
                 //update  data
              }
         }
    }
    

    Activity中,我们可以使用ViewModelProvider来得到 ViewMode的实例。

    如果在ViewModel中,我们需要使用到上下文Context对象(toast 或者获取系统服务等等),我们可以继承AndroidViewModel来构建ViewModel

    class AndroidModel(app:Application): AndroidViewModel(app) {
    
    }
    

    此时这个AndroidModel的创建和上面的类似,也是用ViewModelProvider来获得。

    ViewModel常常结合LiveData使用,然后在我们的Activity或者Fragemnt中去监听LiveData的改变

    然后去在Activity中做UI的更新逻辑,例如,我们需要根据网络请求来决定是否弹出一个DialogFragment,我们的网络请求放在ViewModel中,DialogFragment必须在Activity或者Fragment中弹出来(因为DialogFragment的弹出不能使用Application作为Context),所以此时我们必须使用LiveData。当网络请求完成之后,我们改变LiveData的值,并且在Activity或者Fragment监听LiveData的变化,然后作出弹出DailogFragment的操作

    Fragment之间共享数据

    现在的App中使用Fragment是很常见的,之前我们从Activity中向Fragment中传递数据,我们使用Bundle来传递(在创建Fragment的时候),但是在Activity中如果需要动态传递(随时传递)数据给Fragment,我们平常的做法可能是在Activity中持有Fragment的应用,然后在Activity中去调用对于Fragment的某些方法传递数据,或者利用通知系统(EventBus),但是我们如果在BFragment中促使Activity中的数据改变,要通知到CFragment的Ui修改的话,目前的场景只能使用EventBus。当然也可以使用我们这里的ViewModel

    class ShareViewModel : ViewModel() {
          val selected = MutableLiveData<Item>()
    
          fun select(item: Item) {
             selected.value = item
          }
    }
    
    class ListFragment : Fragment() {
    
        private lateinit var itemSelector: Selector
    
        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: ShareViewModel?=null
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            model = ViewModelProvider(requireActivity()).get(ShareViewModel::class.java)
            itemSelector.setOnClickListener { item ->
                // Update the UI
         }
    }
    
    class DetailFragment : Fragment() {
    
        // Use the 'by activityViewModels()' Kotlin property delegate
        // from the fragment-ktx artifact
        private val model: ShareViewModel?=null
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
        
            model = ViewModelProvider(requireActivity()).get(ShareViewModel::class.java)
            model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
                // Update the UI
        })
    }
    

    在上面的两个Fragment 在获取ViewModel的时候 传递的都是requireActivity(),那么获取到的ViewModel的实例其实就是同一个SharedViewModel,所以当Activity 或者任何一个Fragment中 改变了SharedViewModel中LiveData的数据,都会及时的通知到。这种方法有以下好处:

    Activity 不需要执行任何操作,也不需要对此通信有任何了解。
    除了 SharedViewModel之外,Fragment不需要相互了解。如果其中一个 Fragment消失,另一个 Fragment将继续照常工作。
    每个 Fragment都有自己的生命周期,而不受另一个 Fragment的生命周期的影响。如果一个Fragment替换另一个Fragment,界面将继续工作而没有任何问题。

    ViewModel的源码解析

    image.png

    这里基本只需要知道onCleared()方法就行了,自定义ViewModel并重写这个方法,讲释放资源的逻辑放在这个方法中就行

    ViewModelProvider

    获取ViewModel的实例时,我们是使用ViewModelProvider来获取的

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
    }
    
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
    

    创建ViewModelProvider的时候需要传递一个ViewModelStore 和一个Factory,而我们构建的时候只传递了一个this(Activity),其实就是一个ViewModelStoreOwner

    AppCompatActivity -->FragmentActivity -->ComponentActivity --> ViewModelStoreOwner

    有上面这样一个继承实现关系,我们的AppCompatActivity其实可以说是实现了ViewModelStoreOwner的,最终返回的是ComponentActivity中的mViewModelStore 关于Factory后面再讲

    @NonNull
    @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;
    }
    
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    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();
            }
        }
    }
    

    这里就是获取ViewModelStore的方法,可以看到在ensureViewModelStore方法中,我们会首先判断mViewModelStore 是否为null,然后通过getLastNonConfigurationInstance()得到一个NonConfigurationInstances实例,这里其实就是当Activity旋转的时候ViewModel中的数据还会存在的奥秘,通过nc可以获取重建之前的mViewModelStore,然后从ViewModelStore里面根据类名获取ViewModel的实例,所以获取到的ViewModel在旋转前后其实是同一个实例,在我们的App系统configuration发生改变的时候 就会回调onRetainNonConfigurationInstance()这个方法

    @SuppressWarnings("deprecation")
    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;
    }
    
    //Activity中 的方法
    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
    }
    

    这里就是将ViewModelStore进行保存。 这里getLastNonConfigurationInstance方法 最后其实是返回Activity中的mLastNonConfigurationInstances 变量的activity对象,我们看看这mLastNonConfigurationInstances 在哪里赋值,我们知道,我们的Activity的启动其实最终都会走到ActivityThread类中,当我们启动一个Activity的时候会执行其中的 performLaunchActivity方法最终会调用到 Activityattach方法,lastNonConfigurationInstances是存在ActivityClientRecord中的一个组件信息

    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        Class<? extends Activity> activityClass = null;
        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
        if (r != null) {
            activityClass = r.activity.getClass();
            r.activity.mConfigChangeFlags |= configChanges;
            if (finishing) {
                r.activity.mFinished = true;
            }
    
            performPauseActivityIfNeeded(r, "destroy");
    
            if (!r.stopped) {
                callActivityOnStop(r, false /* saveState */, "destroy");
            }
            if (getNonConfigInstance) {
                try {
                    r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    if (!mInstrumentation.onException(r.activity, e)) {
                        throw new RuntimeException(
                                "Unable to retain activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                    }
                }
            }
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnDestroy(r.activity);
                if (!r.activity.mCalled) {
                    throw new SuperNotCalledException(
                    "Activity " + safeToComponentShortString(r.intent) +
                    " did not call through to super.onDestroy()");
                }
                if (r.window != null) {
                    r.window.closeAllPanels();
                }
            } catch (SuperNotCalledException e) {
                throw e;
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
                        + ": " + e.toString(), e);
                }
            }
            r.setState(ON_DESTROY);
        }
        schedulePurgeIdler();
        // updatePendingActivityConfiguration() reads from mActivities to update
        // ActivityClientRecord which runs in a different thread. Protect modifications to
        // mActivities to avoid race.
        synchronized (mResourcesManager) {
            mActivities.remove(token);
        }
        StrictMode.decrementExpectedActivityCount(activityClass);
        return r;
    }
    

    在屏幕旋转造成的的Activity重建的时候 就会给lastNonConfigurationInstances这个变量赋值,这样就能够在Activity重建的时候 获取到之前的ViewModel了。而我们的ViewModelonClear方法什么时候执行呢,在我们的ComponetActivity构方法中

    public ComponentActivity() {
        // ......省略部分代码
        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();
                    }
                }
            }
        });
    }
    

    上面注册了一个Lifecycle的监听,在我们的ActivityonDestory之后 并且isChangingConfigurations()为false的时候,才会去执行getViewModelStore().clear(); 的操作间接调用到ViewModel的onCleared()方法

    get()获取ViewModel
    在这里通过get方法创建ViewModel传入的参数是 所需要创建的ViewModelClass对象

    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);
    }
    
    
    @SuppressWarnings("unchecked")
    @NonNull
    @MainThread
    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;
    }
    

    最终通过下面的get方法获取ViewModel,当我们从ViewModelStore 根据key值去获取ViewModel为null的时候,如果为null 就是用Factory进行创建。所以后面我们也可以定义自己的Factory 来创建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();
        }
    }
    

    这里其实就是一个 HashMap存放了ViewModel,主要是ViewModel的存取.

    总结

    1.现在的Activity底层已经继承了 ComponentActivity ,并实现了 ViewModelStoreOwner 接口,通过ViewModelProvider使用默认工厂 创建了 viewModel,并通过唯一Key值 进行标识,
    存储到了 ViewModelStore中。等下次需要的时候即可通过唯一Key值进行获取。
    2.由于ComponentActivity 实现了ViewModelStoreOwner接口,实现了 getViewModelStore方法,当屏幕旋转的时候,会先调用 onRetainNonConfigurationInstance()方法
    viewModelStore保存起来,当屏幕旋转之后,会在ensureViewModel()方法中再调用 getLastNonConfigurationInstance方法将数据恢复,如果为空的话,会重新创建viewModelStore
    并存储在全局中,以便以下次发生变化的时候,能够通过onRetainNonConfigurationInstance 保存起来。
    3.最后当页面销毁并且没有配置更改的时候,会将viewModelStore中的数据 进行清除操作。

    相关文章

      网友评论

          本文标题:Jetpack之ViewMode初识,使用和源码讲解

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