JetPack——ViewModel简析

简介

ViewModel以生命周期的方式存储和管理界面相关的数据。让数据在发生屏幕旋转等配置更改后得以继续留存。同时,可以将数据操作从UI控制器(Activity)里分离出来,这样就只需要Activity控制UI逻辑而无需处理数据业务逻辑。在需要进行一些异步操作的时候,免去了在Activity里大量的维护工作,并避免了在Activity销毁时潜在的内存泄漏问题。

总结一下主要优点:

可以更容易的将数据操作逻辑与Activity分离。

ViewModel的实现和基本使用

JetPack为UI控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。

代码如下:

public class MyViewModel extends ViewModel {
private MutableLiveData<List<String>> users;
public LiveData<List<String>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<String>>();
loadUsers();
}
return users;
}

private void loadUsers() {

}
}

使用:


public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

model.getUsers().observe(this, users -> {

});

}
}

ViewModel的创建

注意在上面的代码中,并没有采用new MyViewModel()的方式去创建ViewModel。而是使用:

MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

对应的在kotlin中使用:

private val myViewModel: MyViewModel by viewModels();

先说结论:使用ViewModelProvider去创建ViewModel,确保了重新创建了相同Activity时,它接收的MyViewModel实例与第一个Activity创建的ViewModel实例相同。可以简单的理解为他是一个单例。

接下来看源码是如何实现的:

首先看ViewModelProvider的构造方法和实现:


private final Factory mFactory;

private final ViewModelStore mViewModelStore;

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {

this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory():NewInstanceFactory.getInstance());

}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {

mFactory = factory;

mViewModelStore = store;

}

构造方法很简单,创建一个ViewModelStoreFactory

准备工作

ViewModelStore解析

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

public final void clear() {

for (ViewModel vm : mMap.values()) {

vm.clear();
}

mMap.clear();

}

}

代码很简单,就是一个HashMap存储ViewModel的键值对(HashMap源码要点解析)。

ViewModelStore通过接口ViewModelStoreOwnergetViewModelStore()方法获取,以ComponentActivity为例,具体实现如下:


//ComponentActivity.java:

static final class NonConfigurationInstances {

Object custom;

ViewModelStore 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.");

}

ensureViewModelStore();

return mViewModelStore;

}

@SuppressWarnings("WeakerAccess") /* synthetic access */

void ensureViewModelStore() {

if (mViewModelStore == null) {

NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();

if (nc != null) {

mViewModelStore = nc.viewModelStore;
}

if (mViewModelStore == null) {

mViewModelStore = new ViewModelStore();

}

}

}

其中getLastNonConfigurationInstance方法实际上是返回一个activity,也可以理解为是一个ComponentActivity的实例,在这里不做深入讨论,其在Activity中的具体实现为:


@Nullable

public Object getLastNonConfigurationInstance() {

return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;

}

不难发现,每个Activity都会持有一个mViewModelStoreensureViewModelStore()方法确保了所有ViewModel获取到的ViewModelStore都为同一个。也就说:Activity通过持有mViewModelStore,使用HaspMap管理ViewModel。也不难发现,activity和ViewModel是一对多的关系。

Factory解析

Factory顾名思义,就是一个工厂模式。它是一个定义在ViewModelProvider中的接口,代码很简单:


public interface Factory {

@NonNull

<T extends ViewModel> T create(@NonNull Class<T> modelClass);

}

就是用来创建ViewModel的。

AppCompatActivity为例:它通过继承ComponentActivity,实现了ViewModelStoreOwner接口,而在ViewModelProvider初始化时,结合上面的代码,Factory的具体实现为:NewInstanceFactory.getInstance();

具体代码如下:

public static class NewInstanceFactory implements Factory {

private static NewInstanceFactory sInstance;


@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}

@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
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);
}
}
}

一个静态单例,返回一个用来创建ViewModel的工厂类,使用create()方法通过反射创建ViewModel实例。

真正创建

完成了准备工作, 接下来就看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");

}

//用类名作为Key值

return get(DEFAULT_KEY + ":" + canonicalName, modelClass);

}

@NonNull

@MainThread

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {

//首先查看Map中是否有当前ViewModel的实例

ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {

if (mFactory instanceof OnRequeryFactory) {

((OnRequeryFactory) mFactory).onRequery(viewModel);

}

return (T) viewModel;

} else {

if (viewModel != null) {
}

}

///没有实例就创建实例

if (mFactory instanceof KeyedFactory) {

viewModel = ((KeyedFactory) mFactory).create(key, modelClass);

} else {

viewModel = mFactory.create(modelClass);

}

mViewModelStore.put(key, viewModel);

return (T) viewModel;

}

可以看到,首先使用类名作为key值,判断Activity所持有的ViewModelStore是否包含当前ViewModel的实例,有就直接拿来使用。如果没有,就通过上文中的Factory.create创建一个实例,并添加到mViewModelStore中。

总结一下:

  1. Activity通过HashMap持有所有的实例化后的ViewModel。

  2. ViewModelProvider通过单例工厂模式和ViewModelStore,也就是HashMap实现对ViewModel的创建和获取,以此保证ViewModel在当前Activity中保持单例。

ViewModel的生命周期

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

其生命周期如下图所示:

image.png

通常在Activity的onCreate()方法里创建/获取ViewModel。若在旋转设备屏幕时,再次调用onCreate(),结合上文中ViewModel的创建,其实并不会创建新的ViewModel实例,而是从HashMap中取出本就存在的实例。

因此:ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 activity 完成并销毁。

ViewModel的销毁

销毁源码如下:

@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}

@MainThread
final void clear() {

if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}

其中:clear()方法在ViewModelStore中调用,其相关代码在上文中已展示:

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

ViewModelStore.clear()则在Activity中被调用:

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的生命周期,在Activity销毁时,也就是onDestory时调用。

带参数的ViewModel实现

既然ViewModel不可以通过new关键字来初始化,那么如果需要再初始化时向ViewModel传参该怎么办呢。例如:

public class MyViewModel extends ViewModel {

private String tag;

public MyViewModel(String tag) {
this.tag = tag;
}

private MutableLiveData<List<String>> users;

public LiveData<List<String>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<String>>();
loadUsers();
}
return users;
}

private void loadUsers() {

}
}

根据上文中的代码解析,不难发现,我们需要重写Factorycreate方法,结合ViewModelProvider的构造函数,可以创建一个工厂类:

class MyViewModelFactory implements ViewModelProvider.Factory {
private String tag;

public MyViewModelFactory(String tag) {
this.tag = tag;
}

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MyViewModel(tag);
}
}

具体实现:


MyViewModelFactory myViewModelFactory = new MyViewModelFactory("TAG");

MyViewModel model = new ViewModelProvider(this,myViewModelFactory).get(MyViewModel.class);

当然了,也可以使用匿名内部类的方式实现:

MyViewModel model = new ViewModelProvider(this, new ViewModelProvider.Factory(){
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MyViewModel("tag");
}
}).get(MyViewModel.class);

在kolin中的扩展

koltin中使用扩展函数简化了ViewModel的实现:

@MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)

@MainThread
public fun <VM : ViewModel> Fragment.createViewModelLazy(
viewModelClass: KClass<VM>,
storeProducer: () -> ViewModelStore,
factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}


public class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null

override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also {
cached = it
}
} else {
viewModel
}
}

override fun isInitialized(): Boolean = cached != null
}

其本质上还是用ViewModelProvider创建,只不过加了一些Kotlin的语法糖,简化了操作。

不仅如此,Kotlin还是支持使用注解,这样就可以省去创建Factory的麻烦。刚兴趣的可以自己去探索(Hilt 和 Jetpack 集成

总结

  • Activity持有ViewModel,一个Activity可以有多个类型的ViewModel;

  • 同一个类型的ViewModel在Activity中有且只有一个实例;

  • 通过HashMap和工厂模式保证了单一类型ViewModel的单例;

  • ViewModel生命周期贯穿整个Activity,且不会重复创建。

0 个评论

要回复文章请先登录注册