ViewModel 完全指南:实践与背后原理全解

一、引言

在现代Android应用开发中,处理UI数据的有效管理和状态保持是开发者面临的重要挑战之一。Google推出的Jetpack组件库中的ViewModel已成为解决这些问题的关键工具。ViewModel旨在以生命周期意识的方式存储和管理界面相关的数据,从而使数据在配置更改如屏幕旋转时依然保持不变,极大地提高了应用的稳定性和响应速度。通过本文的走读和代码示例,不仅可以帮助新手开发者快速理解ViewModel的基本用法,还可以使有经验的开发者深入掌握其高级功能和最佳实践。

二、ViewModel基本使用

在Android应用中使用ViewModel可以有效地解决界面数据管理问题。ViewModel是一个负责准备和管理Activity或Fragment中UI所需数据的类,它存在的目的是使数据能够在配置变化时生存下来,例如屏幕旋转。这种机制简化了数据处理流程,并提高了应用的健壮性。

创建ViewModel

创建一个ViewModel通常涉及扩展ViewModel类,并在其中添加存储UI所需数据的变量和方法。下面是一个简单的ViewModel实例,展示了如何存储用户的输入字符串:

import androidx.lifecycle.ViewModel

class UserInputViewModel : ViewModel() {
    var userInput: String = ""
}

这个示例中,UserInputViewModel维护一个名为userInput的字符串。这个字符串在设备配置改变时将不会丢失,保证了用户数据的一致性。

使用ViewModel

在Activity或Fragment中使用ViewModel也非常直观。您需要通过ViewModelProvider来获取ViewModel的实例。这样做确保了配置更改时,您获得的ViewModel实例与之前的是相同的,从而数据得以保留。例如,在一个Activity中使用ViewModel来存储用户输入可以如下操作:

java 复制代码
class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: UserInputViewModel

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

        viewModel = ViewModelProvider(this).get(UserInputViewModel::class.java)

        // Use viewModel to manage UI data
    }
}

通过这种方式,ViewModel作为一个独立于Activity视图控制器的数据管理者,负责管理界面状态,降低了Activity或Fragment的负担,使其能够更专注于处理用户交互。


三、ViewModel的工作原理

ViewModel的核心功能是确保数据在设备配置更改时能够持久保存。这是通过将ViewModel的生命周期绑定到具体的Activity或Fragment的生命周期来实现的,确保ViewModel在屏幕旋转或其他配置变化时不会被销毁。

生命周期管理

当Activity重新创建时,例如屏幕旋转,Activity会被销毁并重新创建。如果没有ViewModel, 所有的界面数据将会丢失。但如果使用ViewModel,这些数据会被保留。这是因为ViewModel对象被设计为比它们服务的Activity或Fragment拥有更长的生命周期。

下面的代码片段展示了ViewModel如何在配置更改期间保持活动状态:

java 复制代码
class ConfigurationViewModel : ViewModel() {
    var configChangeData: String = "Initial Data"

    // Data remains intact across configuration changes
}

```java

在Activity中使用此ViewModel:

class ConfigActivity : AppCompatActivity() {

private lateinit var viewModel: ConfigurationViewModel

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

    viewModel = ViewModelProvider(this).get(ConfigurationViewModel::class.java)

    // Displaying or updating data in the UI
    displayData(viewModel.configChangeData)
}

private fun displayData(data: String) {
    findViewById<TextView>(R.id.data_text_view).text = data
}

}

java 复制代码
通过这种设计,ViewModel使得数据管理独立于Activity或Fragment的生命周期,从而提高应用的稳定性和用户体验。

#### 数据的存储与管理

ViewModel不仅可以存储简单的数据,还可以与LiveData一起使用,实现数据的观察。这使得您可以构建响应式的UI组件,这些组件能够观察数据变化并做出响应。这种模式非常适合开发现代的、交互密集的应用。

```java
class LiveDataViewModel : ViewModel() {
    val liveData = MutableLiveData<String>()

    fun updateData(newData: String) {
        liveData.value = newData
    }
}

在Activity中订阅LiveData:

java 复制代码
class LiveDataActivity : AppCompatActivity() {
    private lateinit var liveDataViewModel: LiveDataViewModel

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

        liveDataViewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)

        // Observing data changes
        liveDataViewModel.liveData.observe(this, Observer { data ->
            findViewById<TextView>(R.id.data_text_view).text = data
        })
    }
}

通过LiveData,ViewModel提供了一种强大的数据管理和通信机制,极大地增强了应用的交互性和灵活性。

四、源码分析

4.分析源码前的准备工作

4.1ViewModel 的生命周期
4.2.几个类的感性认识
  • ViewModelStoreOwner:是一个接口,用来获取一个ViewModelStore对象
  • ViewModelStore:存储多个ViewModel,一个ViewModelStore的拥有者( Activity )在配置改变, 重建的时候,依然会有这个实例
  • ViewModel:一个对 Activity、Fragment 的数据管理类,通常配合 LiveData 使用
  • ViewModelProvider:创建一个 ViewModel 的实例,并且在给定的ViewModelStoreOwner中存储 ViewModel

再例子中的代码

java 复制代码
class ViewModelActivity : AppCompatActivity() {

    //初始化 UserViewModel 通过 ViewModelProvider
    private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val button = Button(this)
        setContentView(button)

        //观察 User 数据,并打印
        userViewModel.userLiveData.observe(this, Observer { user ->
            "User = $user".log()
        })

        //点击按钮更新 User 信息
        button.setOnClickListener {
            userViewModel.updateUser()
        }
    }
}

首先看下UserViewModel的初始化过程。

private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

注:上面代码类似数组的写法是 Kotlin 的写法,其实是 ViewModelProvider 的get方法

5.ViewModelProvider的构造方法,以及 get 方法

5.1ViewModelProvider构造方法

先看ViewModelProvider构造方法,传入的参数为当前的 AppCompatActivity

java 复制代码
//ViewModelProvider.java

private final Factory mFactor
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;
}
  • 通过 ViewModelStoreOwner获取ViewModelStore对象并给 mViewModelStore赋值
  • 给mFactory赋值,这里赋值的是NewInstanceFactory这个对象
5.2.ViewModelProvider的 get 方法
java 复制代码
//ViewModelProvider.java

private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";

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");
    }
  	//1
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

注释1:

  • 调用了两个参数的 get 方法
  • 第一个参数是字符串的拼接,用来以后获取对应 ViewModel 实例的,保证了同一个 Key 取出是同一个 ViewModel
  • 第二参数是 UserViewModel 的字节码文件对象

看下两个参数的get方法

java 复制代码
//ViewModelProvider.java
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);//1
		//2
    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.
        }
    }
  	//3
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
  	//4
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

注释 1:从ViewModelStore中,根据 key,取一个 ViewModel,ViewModelStore源码下文分析

注释 2:判断取出来的 ViewModel 实例和传进来的是否是一个,是同一个,直接返回此缓存中实例

注释 3:通过Factory创建一个ViewModel

注释 4:把新创建的ViewModel用ViewModelStore存储起来,以备下次使用,最后返回新创建的ViewModelStore

这里看一下ViewModel是怎么通过Factory创建出来的

通过 5.1 小节可以知道,这个Factory的实例是NewInstanceFactory

5.3.NewInstanceFactory的create方法
java 复制代码
//ViewModelProvider.java 中的 AndroidViewModelFactory.java
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);
    }
}

简单粗暴,通过反射,直接创建了ViewModel对象。

这里扩展一个,在实例UserViewModel的时候

private val userViewModel by lazy { ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[UserViewModel::class.java] }

也可以通过两个参数的构造方法,来实例化,其中第二个参数就是Factory类型。然后就会用 AndroidViewModelFactory来实例化UserViewModel,我们来具体看下代码

AndroidViewModelFactory是NewInstanceFactory的子类

java 复制代码
//ViewModelProvider.java 中的 AndroidViewModelFactory
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        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);
        }
    }
    return super.create(modelClass);
}

如果我们创建的UserViewModel当初继承的是AndroidViewModel类就走modelClass.getConstructor(Application.class).newInstance(mApplication);实例化方法,否则就走父类的实例化方法,也就是NewInstanceFactory的create方法

在开发中建议使用AndroidViewModel类,它会提供给一个Application级别的 Context。

接下来看一下ViewModelStoreOwner是什么,以及它的具体实现

6.ViewModelStoreOwner

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}
  • 一个接口,里面一个方法返回了ViewModelStore对象
  • 它的实现类在 AndroidX 中ComponentActivity和 Fragment

ComponentActivity的关键代码

//ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner,XXX{
   
    private ViewModelStore mViewModelStore;

    @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.");
        }
        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并返回了

来看下这个ViewModelStore类

7.ViewModelStore

7.1.ViewModelStore的源码

我下面贴的是完整代码,对你没看错。

public class ViewModelStore {
		//1
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
		//2
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
		//3
    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

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

注释 1 :声明一个 Map 来存储ViewModel

注释 2:存储ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get 方法中用到过

注释 3:取出 ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get 方法中用到过。注意在从 Map中去 ViewModel 的时候是根据 Key,也就是7.2小节注释 1 拼接的那个字符串DEFAULT_KEY + ":" + canonicalName 。这也就解释了第 4 节的疑问 为什么在对应的作用域内,保正只生产出对应的唯一实例

注释 4:这个是一个重点方法了,表明要清空存储的数据,还会调用到ViewModel的 clear 方法,也就是最终会调用带 ViewModel 的onCleared()方法

那么这个ViewModelStore的 clear 方法,什么时候会调用呢?

7.2.ComponentActivity的构造方法

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

}

在ComponentActivity的构造方法中,我们看到,在 Activity 的生命周期为 onDestory的时候,并且当前不是,配置更改(比如横竖屏幕切换)就会调用ViewModelStore 的 clear 方法,进一步回调用 ViewModel 的onCleared方法。

这就回答了第四节提出的问题onCleared方法在什么调用

最后看一下 ViewModel 的源码,以及其子类AndroidViewModel

8.ViewModel 的源码

ViewModel类其实更像是更规范化的抽象接口

public abstract class ViewModel {
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    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 的子类AndroidViewModel

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

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

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

提供了一个规范,提供了一个 Application 的 Context

到现在整个源码过程就看了,包括前面,我们提到的那几个关键类的源码。

到目前为止,我们第 4 节抛出的问题,已经解决了,两个了,还有一个ViewModel为什么不会随着Activity的屏幕旋转而销毁;

9.分析为啥ViewModel不会随着Activity的屏幕旋转而销毁

首先知道的是 ViewModel 不被销毁,是在一个 ViewModelStore 的 Map 中存着呢,所以要保证ViewModelStore不被销毁。

首先得具备一个前置的知识

在 Activity 中提供了 onRetainNonConfigurationInstance 方法,用于处理配置发生改变时数据的保存。随后在重新创建的 Activity 中调用 getLastNonConfigurationInstance 获取上次保存的数据。

10.1.onRetainNonConfigurationInstance方法

//ComponentActivity.java
/**
 * Retain all appropriate non-config state.  You can NOT
 * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
 * retain your own non config state.
 */
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
		//1
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

注意看下方法上的注释

  • 不需要也不能重写此方法,因为用 final 修饰
  • 配置发生改变时数据的保存,用ViewModel就行
  • 注释 1:把ViewModel存储在 NonConfigurationInstances 对象中

现在再看下ComponentActivity 的 getViewModelStore方法

//ComponentActivity.java
@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.");
    }
    if (mViewModelStore == null) {
      	//1
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

注释 1:获取了NonConfigurationInstances一个对象,不为空从其身上拿一个ViewModelStore,这个就是之前保存的ViewModelStore

当 Activity 重建时还会走到getViewModelStore方法,这时候就是在NonConfigurationInstances拿一个缓存的ViewModelStore。


ViewModel的高级应用

随着Android应用变得越来越复杂,ViewModel的使用也不再局限于简单的UI数据存储。它的高级用法包括与LiveData结合使用以及实现Repository模式,这些都是提升应用架构清晰度和维护性的关键技术。

结合LiveData使用

LiveData是一个可观察的数据持有者类,它遵循观察者模式,允许数据变化可以通知到观察者组件。与ViewModel结合使用时,LiveData提供了一种响应式方式来更新UI,这样可以确保UI组件总是展示最新的数据。

class UserViewModel : ViewModel() {
    private val userRepository = UserRepository()
    val userData: LiveData<User> = userRepository.getUserData()

    fun updateUser(userData: User) {
        userRepository.updateUser(userData)
    }
}

在Activity或Fragment中,您可以观察这些LiveData对象:

class UserActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel

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

        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        viewModel.userData.observe(this, Observer { user ->
            // 更新UI
        })
    }
}
实现Repository模式

Repository模式是一种将数据源管理逻辑从UI逻辑中分离的设计模式。ViewModel通过使用Repository类,可以从不同的数据源(如网络API、本地数据库)抽象地获取数据,同时保持UI组件的简洁和聚焦。

class UserRepository {
    fun getUserData(): LiveData<User> {
        // 实现从数据库或网络获取用户数据
    }

    fun updateUser(user: User) {
        // 实现更新用户数据到数据库或网络
    }
}

通过这种方式,ViewModel不仅作为数据的直接管理者,同时也协调不同后端服务或数据库的数据流,显著增强了应用的灵活性和可扩展性。


总结

在本文中,我们详细探讨了ViewModel的基本使用、工作原理及其在复杂应用中的高级应用。通过这些分析和代码示例,我们看到ViewModel不仅能够有效管理UI相关的数据,保持数据在配置变化时的持久性,而且通过与LiveData和Repository模式的结合使用,显著提高了应用的响应性和数据处理能力。

关键优点回顾
  • 生命周期意识:ViewModel的设计允许它自然地处理用户界面所需的数据与Activity或Fragment生命周期的关联问题,从而简化了开发者的工作。
  • 数据管理与隔离:通过将数据处理逻辑从UI逻辑中解耦,ViewModel帮助开发者实现更清晰和可维护的代码结构。
  • 增强的应用稳定性和用户体验:保持数据在配置更改时的一致性,避免了应用在如屏幕旋转等情况下的数据丢失,提升了用户体验。

鼓励所有Android开发者实践ViewModel的使用,深入探索其与其他Jetpack组件的集成,以充分利用这些工具提供的强大功能,为用户创造出更流畅、更可靠的应用体验。

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

相关推荐
太空漫步111 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手7 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef14 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb