jetpack之ViewModel

Posted 做一个苦行僧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jetpack之ViewModel相关的知识,希望对你有一定的参考价值。

ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。

摘自官方文档

android 框架可以管理界面控制器(如 Activity 和 Fragment)的生命周期。Android 框架可能会决定销毁或重新创建界面控制器,以响应完全不受您控制的某些用户操作或设备事件。

如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。因配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState()方法中保存从 onCreate()中恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

另一个问题是,界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在为配置更改重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

诸如 Activity 和 Fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。 Activity 和 Fragment 太多的逻辑控制代码降低了代码阅读性和可维护性。

从界面控制器逻辑中分离出视图数据所有权的操作更容易且更高效。

ViewModel 的生命周期

ViewModel对象存在的时间范围是获取 ViewModel时传递给 ViewModelProvider的Lifecycle。ViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle永久消失:对于 activity,是在 activity finished的时候;而对于 fragment,是在 fragment detach时候

我们通常在Activity 的onCreate()方法中创建ViewModel(避免重复创建).系统可能会在Activity的整个生命周期内多次调用onCreate()方法,例如旋转手机屏幕的时候。ViewModel存在的时间范围是从首次创建ViewModel直到activity finished 并销毁

ViewModel的创建

ViewModel使用很简单,我们只需要继承ViewModel类就行了

class ViewModelActivity : AppCompatActivity() 
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        val binding = ActivityViewModelBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val model  = ViewModelProvider(this).get(MyViewModel::class.java)
        model.name.observe(this)
            //update UI 的操作
        
    

在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的时候),但是在Activityt中如果需要动态传递(随时传递)数据给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的源码解析

ViewModel的代码很简单

这里基本只需要知道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 方法最终会调用到Activity的attach方法,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了。而我们的ViewModel中onClear方法什么时候执行呢,在我们的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的监听,在我们的Activity在onDestory之后 并且isChangingConfigurations()为false的时候,才会去执行getViewModelStore().clear(); 的操作间接调用到ViewModel的onCleared()方法

get()获取ViewModel

在这里通过get方法创建ViewModel 传入的参数是 所需要创建的ViewModel的Class对象

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

ViewModelStore类

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的存取

以上是关于jetpack之ViewModel的主要内容,如果未能解决你的问题,请参考以下文章

jetpack之ViewModel

jetpack之ViewModel

jetpack之ViewModel

搞懂Android Jetpack ViewModel 使用及原理

搞懂Android Jetpack ViewModel 使用及原理

Android kotlin 系列讲解(进阶篇)Jetpack系列之LiveData