Jetpack框架探究03:ViewModel组件的使用与源码分析

 ViewModel 具备宿主生命后期感知能力的数据存储组件,使用 ViewModel 保存的数据,在页面因配置变更导致页面销毁重建之后依然也是存在的,其中配置变更主要是指横竖屏切换、分辨率调整、权限变更、系统字体样式变更。ViewModel 的优势:

  • 页面更改数据不丢失

 当设备因配置更改导致 Activity/Fragment 重建,ViewModel 中的数据并不会因此而丢失,配合 LiveData 可以在页面重建后立马能收到最新保存的数据用以重新渲染页面。

  • 生命周期感应

 在 ViewModel 中难免会做一些网络请求或数据的处理,可以复写 onCleared() 方法,终止清理一些操作,释放内存。该方法在宿主 onDestroy 时被调用。

  • 数据共享

 对于单 Activity 对 Fragment 的页面,可以使用 ViewModel 实现页面之间的数据共享,实际上不同的 Activity也可以实现数据共享。

1. ViewModel基本使用

(1)首先,在module的build.gradle中添加依赖;

//通常情况下,只需要添加appcompat就可以了
api 'androidx.appcompat:appcompat:1.1.0'
//如果想单独使用,可引入下面的依赖
api 'androidx.lifecycler:lifecycle-viewmodel:2.0.0'

(2)其次,实现一个与当前Activity/Fragment关联的ViewModel;

  通常一个Activity对应一个ViewModel,Activity可以由多个Fragment组成,因此多个Fragment共享这个ViewModel对象存储的数据(注:实际上是LiveData存储的数据)。当然,多个Activity也可以共享同一个ViewModel,但是需要在Application中特殊配置;

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

(3)最后,实例化一个ViewModel对象。

 在ViewModel中,我们结合LiveData即可实现一个MVVM架构开发模式,其中,ViewModel将充当VM层,负责从M层获取数据并通知V层自动更新UI(LiveData实现)。

// 获取Activity对应的ViewModel
// 该ViewModel的生命周期与Activity一致
val viewModel = ViewModelProvider(activity).get(SharedViewModel::class.java)
// 在Fragment中获取Activity的ViewModel
// 那么Activity与Fragment共享该ViewModel对象
val viewModel = ViewModelProvider(requestActivity()).get(SharedViewModel::class.java)

// 创建一个Fragment的ViewModel对象
// 该ViewModel的生命周期与Fragment一致
val viewModel = ViewModelProvider(fragment).get(SharedViewModel::class.java)

 当然,我们也可以Kotlin的特性更容易的获取ViewModel对象,但需要注意的是,这需要在项目中分别依赖activity-ktxfragment-ktx库。

// Activity及其两个Fragment获得得将是同一个SharedViewModel对象
// 即共享数据
class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val model: SharedViewModel by viewModels()
        model.selected().observe(this, Observer<Item>{ item ->
            
        })
    }
}

class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
            // Update the UI
        })
    }
}

2. ViewModel源码解析

2.1 ViewModel的生命周期

Jetpack框架探究03:ViewModel组件的使用与源码分析

2.2 ViewModel实现原理

 ViewModel框架UML类图如下:

Jetpack框架探究03:ViewModel组件的使用与源码分析

具体说明如下:

  • ViewModelProvider

ViewModelProvider是提供创建ViewModel对象的入口,它的构造方法需要传入一个ViewModelStoreOwner对象和一个Factory对象,其内部将通过工厂模式完成对ViewModel实例的创建。源码如下:

public class ViewModelProvider {
    private static final String DEFAULT_KEY =
            "androidx.lifecycle.ViewModelProvider.DefaultKey";
            
    ...
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
    
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 具体工厂创建ViewModel实例
    }
}
  • ViewModelStoreOwner

ViewModelStoreOwner是一个接口,通过实现该接口,用于表明自己是一个ViewModelStore的拥有者(owner)。Activity/Fragment均继承了这个接口,在Activity/Fragment中,可以通过getViewModelStore()方法获取与其关联的ViewModelStore对象。源码如下:

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

ViewModelStore是一个ViewModel实例存储仓库,它内部维护了一个HashMap集合,实现存储多个ViewModel实例。在Activity/Fragment中,可以通过调用其getViewModelStore()方法来获取与之关联的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();
    }
}
  • Factory

 在ViewModel框架中,是通过工厂模式实现对ViewModel对象的创建,其中,Factory就是所有具体工厂类的父接口,它提供了一个通用的方法create来创建一个ViewModel对象,但具体的实现是在具体工厂类中,如KeyedFactory、SavedStateViewModelFactory 、NewInstanceFactory、AndroidViewModelFactory。源码如下:

public interface Factory {
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

2.2.1 创建ViewModel对象过程

 首先,创建一个ViewModelProvider对象,其构建方法中需要传入一个ViewModelStoreOwner和指定创建ViewModel实例的工厂Factory,这个工厂对象可选,对于Activity和Fragment来说,默认调用ViewModelStoreOwner的getDefaultViewModelProviderFactory()方法获取,因为它们均实现了HasDefaultViewModelProviderFactory接口。代码如下:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        // owner即Activity或Fragment
        // 它们均继承了ViewModelStoreOwner和HasDefaultViewModelProviderFactory
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

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

// ViewModelStore: ViewModel仓库,存储所有ViewModel对象
// factory:创建ViewModel的具体工厂
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

 其次,通过工厂模式实例化一个ViewModel对象,并返回。具体过程如下:

  • a. 检查ViewModelStore仓库中是否缓存了要创建的ViewModel对象;
  • b. 如果没有指定工厂,就使用SavedStateViewModelFactory具体工厂来创建ViewModel对象;
  • c. 将创建的ViewModel对象缓存到ViewModelStore仓库中。
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {

    // 1. 首先,检查是否存在modelClass的缓存
    
    // (1)尝试从ViewModel仓库中获取key对应的ViewModel对象
    // 其中,mViewModelStore = owner.getViewModelStore()
    
    ViewModel viewModel = mViewModelStore.get(key);

    // (2) 核实viewModel是否确实为modelClass的一个实例
    // 如果是,直接return即可
    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.
        }
    }
    
    // 2. 否则,判断mFactory是哪一个工厂实例
    // 并调用对应的create方法创建ViewModel对象
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    
    // 3. 将创建的ViewModel对象缓存到ViewModel仓库中
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

 根据ViewModelProvider的构造方法可知,假如我们在Activity或Fragment中创建ViewModel对象的话,mFactory将是它们的getDefaultViewModelProviderFactory()方法返回的具体工厂对象,即均为SavedStateViewModelFactory。代码如下:

//.../androidx/activity/ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner 
{
    ...
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
            + "Application instance. You can't request ViewModel before onCreate call.");
        }
        // 创建SavedStateViewModelFactory对象
        if (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
            getApplication(),
            this,
            getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
}


// .../androidx/fragment/app/Fragment.java

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
        ViewModelStoreOwner, HasDefaultViewModelProviderFactory, SavedStateRegistryOwner 
{
    ...
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        // 创建SavedStateViewModelFactory对象
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    requireActivity().getApplication(),
                    this,
                    getArguments());
        }
        return mDefaultFactory;
    }
}
    

 接着,我们来看一下SavedStateViewModelFactory#create是如何构建ViewModel对象。具体过程如下:

  • a. 检查要创建的ViewModel是否包含参数savedStateHandle的构造方法,分实现AndroidViewModel与否;
  • b. 如果不存在(a)描述的构造方法,则使用AndroidViewModelFactory具体工厂创建ViewModel对象;
  • c. 否则,使用(a)返回的构造方法反射实例化一个ViewModel对象。
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
    // 1. 首先,查找是否存在modelClass的构造方法
    // 根据modelClass是否继承于AndroidViewModel分两种情况
    // 如果是,查找含有(application, savedStateHandle)参数的构造方法;
    // 否则,查找含有(savedStateHandle)参数的构造方法;
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    Constructor<T> constructor;
    if (isAndroidViewModel) {
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // doesn't need SavedStateHandle
    // 2. 其次,如果没有找到
    // 说明要创建的ViewModel不包含对应的构造方法
    // 此时也说明,这里不需要SavedStateHandle
    if (constructor == null) {
        return mFactory.create(modelClass);
    }

    // 3. 找到对应的构造方法
    // 处理需要SavedStateHandle的情况(本文暂时不考虑)
    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
        T viewmodel;
        if (isAndroidViewModel) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
        return viewmodel;
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Failed to access " + modelClass, e);
    } catch (InstantiationException e) {
        throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException("An exception happened in constructor of "
                + modelClass, e.getCause());
    }
}

 由SavedStateViewModelFactory的构造方法可知,上述代码的mFactory即位AndroidViewModelFactory的一个实例。代码如下:

public SavedStateViewModelFactory(@NonNull Application application,
        @NonNull SavedStateRegistryOwner owner,
        @Nullable Bundle defaultArgs) {
    mSavedStateRegistry = owner.getSavedStateRegistry();
    mLifecycle = owner.getLifecycle();
    mDefaultArgs = defaultArgs;
    mApplication = application;
    
    // 单利模式创建AndroidViewModelFactory
    mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}

 AndroidViewModelFactory继承于NewInstanceFactory,它们均是Factory的具体工厂实现类,需要注意的是,如果我们自定义的ViewModel继承于AndroidViewModel,那么就使用AndroidViewModelFactory具体工程来实例化该对象,否则,使用NewInstanceFactory具体工程来实例化。代码如下:

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

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

    private Application mApplication;

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

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        // 1. 如果modelClass是AndroidViewModel的子类
        // 调用其指定的构造方法来实例化一个对象
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        // 2. 否则,调用父类NewInstanceFactory的create方法
        // 该方法会使用默认的构造方法实例化一个对象
        // modelClass.newInstance()
        return super.create(modelClass);
    }
}

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

2.2.2 ViewModel复用原因分析

 由ViewModelProvider$get方法可知,当ViewModel被实例化完毕后,将会被存储到ViewModel的仓库ViewModelStore中(注:Activity可以关联多个ViewModel对象),所有Activity与其关联的ViewModel对象都会缓存到这个ViewModelStore的Map集合中。在Activity获取ViewModelStore对象如下:

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.");
    }
    // 1. 判断ViewModelStore是否存在,没有存在则创建一个
    if (mViewModelStore == null) {
        //(1) 查看NonConfigurationInstances中是否有缓存
        // 如果有就返回这个缓存
        // static final class NonConfigurationInstances {
        //     Object custom;
        //     ViewModelStore viewModelStore;
        //  }
        // 注:NonConfigurationInstances为ComponentActivity的静态内部类
        // 被final修饰,即不可被继承
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        //(2) 否则,直接new一个
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    // 2. 否则,直接返回已经存在的ViewModelStore
    return mViewModelStore;
}

 由上述代码可知,getViewModelStore()方法中会首先从NonConfigurationInstances来获取ViewModelStore实例对象。那么,ViewModelStore何时被存储到NonConfigurationInstances?答案是:onRetainNonConfigurationInstance。因系统原因页面被回收时,会触发该方法,所以 viewModelStore 对象此时会被存储在NonConfigurationInstance 中。在页面恢复重建时,会再次把这个 NonConfigurationInstance 对象传递到新的Activity 中实现对象复用。onRetainNonConfigurationInstance()源码如下:

public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 如果NonConfigurationInstance保存了viewModelStore,把它取出来
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    //把viewModelStore放到NonConfigurationInstances中并返回
    nci.viewModelStore = viewModelStore;
    //把viewModelStore放到NonConfigurationInstances中并返回
    return nci;
}

2.2.3 ViewModel#onClear()被调用过程

 ViewModel的onClear()方法是在Activity被销毁被回调,即通过监听Activity的Lifecycle实现。当生命周期为onDestory时,会去调用ViewModelStore的clear()方法,在该方法中会遍历集合中的ViewModel对象,并依次调用其onClear()方法,最后,再将集合清空。相关源码如下:

// .../androidx/activity/ComponentActivity.class
public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    ...
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
}

// .../androidx/lifecycle/ViewModelStore.class
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

// .../androidx/lifecycle/ViewModel.class
protected void onCleared() {
}

@MainThread
final void clear() {
    mCleared = true;
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

 强调一点:ViewModel只是针对于页配置变更时,ViewModel能够实现复用,对于因内存不足或者因为电量不足导致页面被回收情况,是无法实现ViewModel复用的,此时就需要ViewModel配合SavedState实现,它将承担起ViewModel与onSaveIntanceState之间通信的桥梁,其中,onSaveIntanceState在非配置变更导致页面被回收时被触发。最后,总结一下ViewModel与onSaveIntanceState方法区别:

  • onSaveIntanceState

onSaveIntanceState只能存储轻量级的 key-value 键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在 ActivityRecord 中;

  • ViewModel

ViewModel可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中。

Github源码:ExampleJetpack

上一篇:随着日益增多的新技术,Android开发接下来的路该怎么走?


下一篇:【视频】自然框架之分页控件的使用方法(一) PostBack方式的一般分页方式