ViewModel浅析

孔正文
2023-12-01

本篇文章不关注ViewModel是如何使用的,主要讨论的是ViewModel的原理以及它是如何创建的

ViewModel的创建

我们在创建ViewModel的时候,有多种方式,比如,我们可以直接调用它的构造方法

val model = MyViewModel()

我们也可以使用下面的方法来创建

val model = ViewModelProviders.of(this).get(MyViewModel::class.java)

注意,使用ViewModelProviders需要导入下面的依赖

implementation "android.arch.lifecycle:extensions:1.1.1"

说明:不推荐直接调用ViewModel的构造参数去创建一个ViewModel,因为ViewModel的主要目的是去保存数据,比如在我们的Activity翻转的时候,ActivityonDestroy方法会被调用,然后Activity的构造方法会被调用,然后ActivityonCreate方法会被调用,ViewModel的目的就是确保,销毁前的Activity与重新创建的Activity可以获取到同一个ViewModel,从而达到数据保存的目的,我们通过ViewModelProviders.of(..).get(..)的方式,就可以确保获取到的ViewModel是同一个ViewModel,但是,如果我们是直接调用ViewModel的构造参数去获取ViewModel,那么销毁前的Activity与重新创建的Activity获取到的不是同一个ViewModel

最近,发现了有一种新的创建ViewModel的方式,就是调用ViewModelProvider的构造方法,创建一个ViewModelProvider,注意不是**ViewModelProviders**

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

然后调用ViewModelProviderget方法,传入要构造的ViewModelClass对象,去获取一个ViewModel

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

那么,我们的疑问是,ViewModelProvider构造参数中的ViewModelStoreFactory是什么呢?它与我们使用ViewModelProviders.of(..).get(..)的方式创建ViewModel有什么联系吗?我们后面会一一进行分析。

ViewModelProviders.of.get

我们先分析ViewModelProviders.of(..).get(..)是如何创建ViewModel的,这有利于我们后面的理解。

创建ViewModelProvider

我们使用下面的语句来创建ViewModel

val model = ViewModelProviders.of(this).get(MyViewModel::class.java)

首先查看ViewModelProviders.of方法,这个方法用于创建ViewModelProvider

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }

调用了自己的重载方法,第二个参数传入了null

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        // 检查是否能获取到Application,如果可以则获取,不可以则抛出异常
        Application application = checkApplication(activity);
        // 若factory为null,那么就获取AndroidViewModelFactory,并赋值为factory
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        // 获取activity的ViewModelStore,然后创建ViewModelProvider,构造参数为
        // ViewModelStore和factory
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

AndroidViewModelFactory是创建ViewModel的一个工厂,ViewModelStore用于存储ViewModelgetViewModelStore方法定义在ViewModelStoreOwner接口中,ComponentActivity实现了该接口

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

另外,Fragment类也实现了ViewModelStoreOwner接口,在Fragment中也可以调用getViewModelStore方法获取对应的ViewModelStore

上面提到AndroidViewModelFactory,它的定义如下

    public static class AndroidViewModelFactory extends 
        					ViewModelProvider.NewInstanceFactory {

        // AndroidViewModelFactory采取单例的方式进行定义
        private static AndroidViewModelFactory sInstance;

        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application 
                                                          application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        // AndroidViewModelFactory持有Application的引用
        private Application mApplication;

        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }

        @NonNull
        @Override
        // 创建ViewModel的方法
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                try {
                    return modelClass.getConstructor(Application.class)
                        			 .newInstance(mApplication);
                } catch ...
            }
            return super.create(modelClass);
        }
    }

继承关系:AndroidViewModelFactory -> NewInstanceFactory -> Factory,它们都定义在ViewModelProvider里面,Factory接口定义了create方法,用于创建ViewModel

我们上面调用了ViewModelProvider的构造方法,它里面其实就是将ViewModelStoreFactory保存起来了

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

总结一下,ViewModelStore就是Activity中的ViewModelStore,它用于存储ViewModel,由于我们没有主动指定Factory,所以这里Factory就是系统默认的AndroidViewModelFactory

创建ViewModel

调用ViewModelProviderget方法,就可以创建ViewModel

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

调用了自己的重载方法get,传入两个参数,第一个参数是key,用于标识ViewModel,这里使用ViewModel的「包名+类名」来标识ViewModel,第二个参数是ViewModel对应的Class对象

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 在ViewModelStore中查找是否有key对应的ViewModel
        ViewModel viewModel = mViewModelStore.get(key);

        // 该方法类似于instanceof的语句,如果viewModel对应的类型,是modelClass对应的类型,或者
        // 是modelClass的子类类型,那么该方法返回true,否则返回false。如果viewModel为null,该方
        // 法也返回false
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            // 利用AndroidViewModelFactory去创建一个ViewModel
            viewModel = (mFactory).create(modelClass);
        }
        // 将ViewModel存入ViewModelStore中
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        // 返回ViewModel
        return (T) viewModel;
    }

这里我们需要关注三点:

  1. AndroidViewModelFactory如何创建ViewModel
  2. ViewModelStore是如何缓存ViewModel
  3. ViewModelStore是如何获取的

AndroidViewModelFactory创建VM

上面调用了AndroidViewModelFactorycreate方法创建了一个ViewModel,它的create方法如下

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            // 如果AndroidViewModel是modelClass对应的类型的父类,该方法返回true,进入If
            // 语句,否则该方法返回false
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch ...
            }
            // 调用父类的create方法
            return super.create(modelClass);
        }

其实AndroidViewModel就是内部包含了一个Application对象的ViewModel,一般我们使用ViewModel的时候也没有去继承AndroidViewModel,所以这里我们就认为它不会进入If语句,而是直接调用了父类的create方法。

AndroidViewModelFactory的父类是NewInstanceFactory,它的create方法如下

    public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

我们可以看到这里调用了modelClassnewInstance方法,也就是modeClass对应的ViewModel需要有一个空参数的构造方法,否则就会构造失败,抛出异常。

所以,如果我们使用ViewModelProviders.of.get的方式构造ViewModel,那么只可以构造出无参数的ViewModel,如果我们的ViewModel的构造方法是有参数的,通过ViewModelProviders.of.get的方式将会构造失败,这时候我们就需要自定义Factory来构造ViewModel

ViewModelStore的缓存

我们直接看下ViewModelStore的源码

public class ViewModelStore {

    // 存储ViewModel的Map
    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    // 存入ViewModel
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
    	// 如果有旧的ViewModel与key对应,就调用旧的ViewModel的onCleared方法
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
    
	// 根据key获取ViewModel,若key没有对应的ViewModel,该方法会返回null
    final ViewModel get(String key) {
        return mMap.get(key);
    }

    // 返回key的集合
    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    // 调用所有ViewModel的clear方法,通知它们可以销毁了,并且清理干净mMap
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore的获取

ViewModelStore是在创建ViewModelProvider的时候获取的

    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

这里调用了activitygetViewModelStore方法获取ViewModelStore,该方法如下

    @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.");
        }
        
        // 判断mViewModelStore是否为null
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                // 查看是否可以从nc恢复ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            // 若没有恢复成功,就新创建一个ViewModelStore
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        // 返回ViewModelStore
        return mViewModelStore;
    }

这里我们分为三种场景分别讨论ViewModelStore的获取

  1. Activity是新创建的:这时候mViewModelStorenc均为null,那么就新构造一个ViewModelStore并返回。
  2. 在场景1的基础上,Activity已经创建了一个mViewModelStore,但是Activity由于配置更改,例如横竖屏的切换,导致Activity销毁又重建:这时候mViewModelStorenull,但是nc不为null,并且从nc当中获取到的viewModelStore也不为null,这时候重建的Activity和销毁前的Activity拿到的ViewModelStore是同一个,然后就可以通过同一个ViewModelStore,去获取之前的ViewModel,以达到配置更改但是数据得到保存的目的
  3. 在场景1的基础上,Activity已经创建了一个mViewModelStore,这时候我主动去销毁该Activity,然后又重新启动该Activity:该场景和场景1一样,即mViewModelStorenc均为null,那么就新构造一个ViewModelStore并返回。

小结

到了这里,我们就明白了

  1. 通过ViewModelProviders.of.get的方式是如何做到,在横竖屏切换时,销毁前的Activity与重新创建的Activity可以获取到同一个ViewModel
  2. ViewModelProviders.of.get默认只可以构造没有参数的ViewModel,如果ViewModel带有参数,我们就需要自定义构造ViewModelFactory

自定义Factory

例如现在我们的ViewModel构造方法是带有参数的

class MyViewModel(val arg: Int) : ViewModel() {
    ...
}

那么我们就需要自定义一个Factory,在它的create方法中,调用MyViewModel的带有参数的构造方法,创建MyViewModel并返回

class VMFactory(val arg: Int) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MyViewModel(arg) as T
    }
}

接着在Activity当中,我们只需要主动地去构造ViewModelProvider,传入我们自定义的Factory,然后调用ViewModelProviderget方法,即可获取MyViewModel的实例

class ViewModelActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        val model = ViewModelProvider(viewModelStore, 
                                      VMFactory(10)).get(MyViewModel::class.java)

       	...
    }
}

这里构造ViewModelProvider是使用了和ViewModelProviders.of.get一样的构造方法

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

但是传入一个ViewModelStore总归是有点别扭,其实ViewModelProvider还有另外一个构造方法

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

这个方法中,我们只需要传入一个ViewModelStoreOwner,然后它就会获取ownerViewModelStore,然后自动调用上面的含ViewModelStore,Factory参数的构造方法。

ComponentActivityFragment都实现了ViewModelStoreOwner接口,可以调用getViewModelStore方法,获取ViewModelStore

参考

  1. Android开发 ViewModel_2_了解多种自定义实例方式 - 博客园.
 类似资料: