Android jetpack 之 ViewModel

首先,ViewModel是什么.
ViewModel是一个以注重生命周期存储和管理界面相关数据的类。ViewModel可以让数据在发生如屏幕旋转等配置更改后继续存留。
比如我现在在Activity里定义了一个Int型的变量x初始化为0。在Activity运行期间,我点击了一个按钮让x的值变为1。假如此时我旋转,等屏幕旋转后,Activity重建。那x的值会被重新设置为0。现在我们使用ViewModel,让x的值在屏幕旋转之后还是1。

开始使用ViewModel
实现一个ViewModel我们可以创建一个类来继承ViewModel类,并在里面定义一个Int型的变量,初始化为0.

class NumViewModel : ViewModel() {
    var num: Int = 0
}

接着在Activity的onCreate()方法中获取我们的NumViewModel (在依赖了Android KTX 中的 Fragment KTX模块后,可以直接使用viewModels和activityViewModels属性委托绑定到ViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val model = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
        .get(NumViewModel::class.java)
}

现在我点击一个按钮让NumViewModel中的x的值变为1。假如此时旋转屏幕,在Activity重建后,NumViewModel中x的值还是为1。

使用ViewModel在Fragment之间共享数据
我们可以使用ViewModel在Fragment之间共享数据,只要我们的Fragment获取ViewModel时是使用的是包含他们的Activity,这里使用的是requireActivity()方法,他返回一个不为空的Activity。此时他们获取的是同一个SharedViewmodel的实例

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData()
    fun select(string: String) {
        selected.value = string
    }
}

class OneFragment : Fragment(){
    private lateinit var sharedViewModel: SharedViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedViewModel = ViewModelProvider(requireActivity(),
            ViewModelProvider.NewInstanceFactory()).get(SharedViewModel::class.java)
    }
}

class TwoFragment : Fragment(){
    private lateinit var sharedViewModel: SharedViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedViewModel = ViewModelProvider(requireActivity(),
            ViewModelProvider.NewInstanceFactory()).get(SharedViewModel::class.java)
    }
}

ViewModel对象的获取
我们在创建ViewModel对象的时候使用了ViewModelProvider下面是它的构造方法:

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

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

在上面的构造方法中,需要传递一个ViewModelStoreOwner和一个Factory,ViewModelStoreOwner是一个接口,提供了一个获取ViewModelStore的方法,而我们的FragmentActivity已经实现了这个接口,所以我们在上面的Activity中直接传递了一个this。
接下来我们看一下FragmentActivity获取ViewModelStore的过程。

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

public class FragmentActivity extends ComponentActivity implements
        ViewModelStoreOwner,
        ActivityCompat.OnRequestPermissionsResultCallback,
        ActivityCompat.RequestPermissionsRequestCodeValidator {
    ...
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        ...
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
    ...
}

可以看到,在获取ViewModelStore的过程中,会首先获取一个NonConfigurationInstances,在从该对象中获取ViewModelStore。如果NonConfigurationInstances中的ViewMdeolStore为空,才会去新建一个ViewModelStore。
下面是ViewModelStore。

public class ViewModelStore {
    //使用一个HashMap来维护我们的ViewModel
    //是因为一个Activity或Fragment中可能拥有多个ViewModel
    private final HashMap mMap = new HashMap<>();

    //将ViewModel对象添加进mMap中
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    //调用get方法获取ViewModel对象
    final ViewModel get(String key) {
        return mMap.get(key);
    }
    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

接着我们看一下Factory,这是ViewModelProvider里面的一个接口。提供了创建一个ViewModel的接口。而我们使用了一个它的实现类NewInstanceFactory

public interface Factory {
    @NonNull
     T create(@NonNull Class modelClass);
}

public static class NewInstanceFactory implements Factory {

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

接着我们看一个ViewModelProvider的get()方法

@NonNull
@MainThread
public  T get(@NonNull Class 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);
}

@NonNull
@MainThread
public  T get(@NonNull String key, @NonNull Class modelClass) {
    //首先从ViewModelStore中获取ViewModel
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

到这里我们看完了获取ViewModel的整个流程。
首先,我们通过Activity或Fragment获取一个ViewModelStore对象,在该对象的内部维护了一个HashMap用来存储我们Activity或Fragment的ViewModel。使用HashMap的原因是因为一个Activity或Fragment可能拥有多个的ViewModel。
接着,通过ViewModelStore对象的来获取一个ViewModel对象,判断一下获取的ViewModel对象能不能强转成我们传递进来的ViewModel类型。如果能,直接返回该对象,如果不能则通过Factory创建一个ViewModel对象,而Factory是通过反射创建ViewModel对象的。
最后,将Factory创建的ViewModel对象存储进ViewModelStore中,接着返回该对象。下次调用就可以直接返回ViewModelStore中存储的ViewModel对象。

现在我们看到,如果创建ViewModelProvider对象时使用的是同一个ViewModelStoreOwner实例的话。得到的ViewModel将是同一个对象,这样我们就可以共享ViewModel里面的数据了

ViewModel是如何在Activity重建后还保存有原来的ViewModel的
在我们因为配置的变动,如旋转屏幕时重建Activity时,AMS会调用到ActivityThread里的handleRelaunchActivity()方法

#ActivityThread
@Override
public void handleRelaunchActivity(ActivityClientRecord tmp,
        PendingTransactionActions pendingActions) {
    ...
    r.activity.mChangingConfigurations = true;
    ...
    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    ...
}

在该方法内部会将Activity的成员变量mChangingConfigurations设置为true,这一步很重要。接着调用handleRelaunchActivityInner()

#ActivityThread
private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
        List pendingResults, List pendingIntents,
        PendingTransactionActions pendingActions, boolean startsNotResumed,
        Configuration overrideConfig, String reason) {
    ...
    //准备关闭Activity
    handleDestroyActivity(r.token, false, configChanges, true, reason);
    ...
    //重新启动Activity
    handleLaunchActivity(r, pendingActions, customIntent);
}

#ActivityThread
@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {
    // getNonConfigInstance为true
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance, reason);
    ...
}

#ActivityThread
//该方法在Activity的onDestroy()前调用
ActivityClientRecord performDestroyActivity(...boolean getNonConfigInstance, ...) { 
    ...
    // getNonConfigInstance为true
    if (getNonConfigInstance) {
        try {
            //保存数据
            r.lastNonConfigurationInstances
                = r.activity.retainNonConfigurationInstances();
        } catch (Exception e) {
           ...
        }
    }
    ...
}

Activity的retainNonConfigurationInstances()方法又会调用onRetainNonConfigurationInstance()方法

//在原本的Activity因配置改变而关闭之前调用
#Activity
NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    ...
    return nci;
}

//在原本的Activity因配置改变而关闭之前调用
#FragmentActivity
@Override
public final Object onRetainNonConfigurationInstance() {
    ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = mViewModelStore;
    nci.fragments = fragments;
    return nci;
}

到这里,就可以看到。在配置更改导致的原有的Activity关闭前,会将原本Activity内的ViewModelStore对象最终绑定到ActivityThread中的ActivityClientRecoed对象的lastNonConfigurationInstances属性上。当Activity关闭回调onDestory()时,由于mChangingConfigurations为true,ViewModelStore也不会清除里面的ViewModel。

接着Activity会重建,在重建时,会在attach方法中将原来保存在ActivityClientRecoed对象上的lastNonConfigurationInstances赋值给Activity的mlastNonConfigurationInstances

#Activity
final void attach(..., lastNonConfigurationInstances, ...){
    ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    ...
}

现在,重新获取ViewModelStore时,由于mLastNonConfigurationInstances不为空,会直接获取里面的ViewModelStore对象。而这个对象就是重建前Activity的。

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    ...
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

到这里,Activity重建后就会重新连接到原来的ViewModelStore了。而ViewModelStore里又保存有原来的ViewModel。所以,ViewModel还是原来的。那么自然数据也就不会变化了。

你可能感兴趣的:(android)