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的生命周期

image

2.2 ViewModel实现原理

 ViewModel框架UML类图如下:

image

具体说明如下:

  • 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

已标记关键词 清除标记
<p> <strong><span style="font-size:20px;color:#FF0000;">本课程主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者</span></strong> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">1. 包含:<span style="color:#FFFF00;background-color:#FF0000;">项目源码、</span><span style="color:#FFFF00;background-color:#FF0000;">项目文档、数据库脚本、软件工具</span>等所有资料</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">2. 手把手的带你从零开始部署运行本套系统</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">3. 该项目附带的源码资料可作为毕设使用</span></strong></span> </p> <p> <span style="color:#FF0000;"><strong><span style="font-size:18px;">4. 提供技术答疑和远程协助指导</span></strong></span><strong><span style="font-size:18px;"></span></strong> </p> <p> <br /> </p> <p> <span style="font-size:18px;"><strong>项目运行截图:</strong></span> </p> <p> <strong><span style="font-size:18px;">1)系统登陆界面</span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015433522.png" alt="" /><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">2)学生模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241015575966.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">3)教师模块</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016127898.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">4)系统管理员</span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016281177.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><img src="https://img-bss.csdn.net/202002241016369884.png" alt="" /></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p> <p> <strong><span style="font-size:18px;"><strong><span style="font-size:18px;">更多Java毕设项目请关注我的毕设系列课程 <a href="https://edu.csdn.net/lecturer/2104">https://edu.csdn.net/lecturer/2104</a></span></strong></span></strong> </p> <p> <strong><span style="font-size:18px;"><br /> </span></strong> </p>
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页