通过源码我们能知道些什么内容
  • 生命周期比组件的长如何实现?
  • 数据在发生屏幕旋转等配置更改时如何保存数据?(注意是配置文件更改而不是所有的activity销毁都保存数据)
  • 如何避免内存泄漏?
  • 如何在 Activity 中的两个或更多 Fragment 共享数据?

Tip: 源码:Android API 29

ViewModel 的使用

根据 ​​sunFlower​​示例,我们写个简单的示例demo如下:

class GardenActivity : AppCompatActivity() {

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

val model: MyViewModel by viewModels{
InjectorUtils.provideUserViewModelFactory()
}

// val model = ViewModelProvider(this,InjectorUtils.provideUserViewModelFactory())

model.getUserInfo()?.observe(this, Observer<User>{ user ->
// update UI
})
}
}

object InjectorUtils {

// 注入数据参数
fun provideUserViewModelFactory(): MyViewModelFactory {
val repository = UserRepository.getInstance()
return MyViewModelFactory(repository)
}
}

// ViewModel 工厂类
class MyViewModelFactory(
private val userRepository: UserRepository,
) : ViewModelProvider.Factory {

@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(userRepository) as T
}
}

// ViewModel
class MyViewModel(private val userRepository: UserRepository ):ViewModel() {
private var userInfo: MutableLiveData<User>? = null

fun getUserInfo(): LiveData<User>? {
if (userInfo == null) {
userInfo = MutableLiveData()
loadUserInfo()
}
return userInfo
}

private fun loadUserInfo() {
this.userInfo = userRepository.getUserInfo()
}
}

// User 仓库
class UserRepository {

fun getUserInfo():MutableLiveData<User>{
val user = MutableLiveData<User>()
user.value = User("张三","18")
return user
}

companion object {

// For Singleton instantiation
@Volatile
private var instance: UserRepository? = null

fun getInstance() =
instance ?: synchronized(this) {
instance ?: UserRepository().also { instance = it }
}
}
}

// User 实体
data class User(val name:String,val age:String)

以上示例实现了数据和 UI 分离,并且​​ViewModel​​中没有持有​​View​​ 接下来我们带着开头的几个问题,深入源码看看它是如何实现的。

(1) 生命周期比组件的长如何实现

Android - 带着问题看源码之 ViewModel_移动开发

上图是​​Activity​​和 ​​ViewModel​​ 生命周期对比图,从图中可看出 ​​ViewModel​​ 的生命周期确实比​​Activity​​长。那么 ​​ViewModel​​ 它是如何实现的呢?

其实主要用到了​​Jetpack 中的 LifeCycle​​库,当然不用该库也是可以的,下篇文章我们再分析该库的实现。

首先我们要明确知道 ​​ViewModel​​ 的作用之一就是是:通过关联生命周期的方式来存储和管理跟UI相关的数据 而​​LifeCycle​​库是专门用来处理生命周期的库,也就是该库可以感知​​Activity​​的生命周期,并在不同的生命周期处理相关的逻辑。

上图也看出了 ​​ViewModel​​的生命周期比​​Activity​​的​​onDestory​​生命周期还长并且多了个方法​​onCleared​​。 既然是在 Activity 的 onDestory 生命周期之后,那我们跟进源码看看它是怎么处理的。

源码: Android API 29 查看顺序:AppCompatActivity—>FragmentActivity—>ComponentActivity

AppCompatActivity.java 中只是委托了事件,具体的处理逻辑要在不同的 Android API 版本中处理,这就不在本文的介绍范围了,可搜索 Activity 的加载流程详细了解

@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}

FragmentActivity.java 中处理了 ON_DESTROY 事件

@Override
protected void onDestroy() {
super.onDestroy();
mFragments.dispatchDestroy();
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}

ComponentActivity.java 的构造函数里面观察了 Lifecycle.Event.ON_DESTROY 事件,并获取 ViewModelStore 调用 clear 方法清除数据

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) {

// isChangingConfigurations 方法是检查配置文件是否修改,如果修改则返回true,没有修改则返回false
// 这里取反,就是保证了在配置文件修改的时候不会清除数据
if (!isChangingConfigurations()) {

// 清除数据
getViewModelStore().clear();
}
}
}
});
}

从上面的源码分析也看出了​​ViewModel​​确实是在​​Activity​​的生命周期​​onDestory​​之后监听到​​Lifecycle.Event.ON_DESTROY​​再去解决​​ViewModel​​中的数据清除问题。 而源码中也做了判断,在页面配置文件修改后并不会清除数据。这也进一步说明了​​ViewModel​​能解决因页面配置文件修改后清除数据的问题。

具体如何保存和如何恢复数据,我们知道在配置文件后页面会销毁和重建,这个过程中会使用到下面两个方法。

  • onSaveInstanceState
  • onRestoreInstanceState

那么我们去源码中找找看有没有什么蛛丝马迹

(2) 页面配置修改后如何保存数据

源码: Android API 29 查看顺序:AppCompatActivity—>FragmentActivity—>ComponentActivity

AppCompatActivity.java 同样的这里也只是委托了事件
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
getDelegate().onSaveInstanceState(outState);
}

FragmentActivity.java 中处理了数据的保存

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
markFragmentsCreated();
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);

int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
}
outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
}
}

ComponentActivity.java 的 onSaveInstanceState 方法中还是没有 ViewModel 的身影,但是紧接着的方法 onRetainNonConfigurationInstance 的注释有点意思,它是 final 所以不能重写,但是它里面又有一个公开的 onRetainCustomNonConfigurationInstance 方法,所以说我们可以重写该方法返回一些 Object 的对象,但是该方法过时了。

推荐我们使用 ViewModel 来实现。

@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}

/**
* 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;
}

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}

所以我们还是回到 ComponentActivity.java 的构造方法中找到 Lifecycle.Event.ON_DESTROY 的处理,

在里面有个方法 getViewModelStore 获取到了 ViewModel 的数据,它的实现如下:

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

如果不使用 ​​ViewModel​​,在页面销毁和重建时可以用重写下面两个方法保存和恢复数据

  • onSaveInstanceState
  • onRestoreInstanceState

但是我们知道这两个方法只能保存 Bundle 支持的数据,要想保存其它数据就得用到下面的方法了

  • onRetainNonConfigurationInstance:final 所以不能重写,作用是保存数据,调用时机是​​onStop()​​和​​onDestroy()​​之间
  • onRetainCustomNonConfigurationInstance:@Deprecated 过时了,推荐使用 ViewModel
  • getLastNonConfigurationInstance:作用是取出数据,调用时机是​​onCreate()​​之后
  • getLastCustomNonConfigurationInstance:@Deprecated 过时了,推荐使用 ViewModel

如果使用​​ViewModel​​,在页面销毁和重建时就不需要你操心数据的保存和恢了。

​ViewModel​​会在​​onRetainNonConfigurationInstance​​ 方法中保存数据,如下:

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

如果​​Activity​​重写了​​onRetainCustomNonConfigurationInstance​​方法还可以将自定义的​​Object​​类型的对象保存到​​NonConfigurationInstances 的 custom​​属性中。如下:

NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;

并且还会在​​Activity​​的​​onDestory​​生命周期判断页面是否重建,重建则不删除​​ViewModel​​中的数据

(3) 如何避免内存泄漏?

源码: Android API 29

首先我们要知道内存为何会泄漏。

短生命周期的对象被长生命周期的对象一直持有,导致短生命周期的对象在本该被回收的时候没有及时回收,导致内存泄漏。

比如:

内部类导致的内存泄漏如:非静态内部类 Handler、匿名内部类 Thread 中存在业务处理的时间比 Activity 的生命周期还长,这就会导致内存泄漏

因为内部类默认会持有外部类的引用,当外部类 Activity 关闭需要回收的时候,它的内部类还在持有它,导致它没有被回收。

在文章开头​​ViewModel​​的使用中用到了​​LiveData​​库。 它是具有生命感知的数据存储类,这种感知能力可确保 ​​LiveData​​ 仅更新处于活跃生命周期状态的应用组件观察者。

活跃生命周期状态也就是用户可见的状态下才会更新数据。详细的实现后面深入源码查看并写篇文章记录。

又因为​​ViewModel​​会在​​onDestory​​之后会自动清除相关数据,结合这两点即可避免内存泄漏。

(4) 如何在 Activity 中的两个或更多 Fragment 共享数据?

在创建 ViewModel 的时候需要传入​​this​​上下文,这个上下文是个​​LifecycleOwner​​, ​​AppCompatActivity 和 Fragment​​都默认实现了​​LifecycleOwner​​接口,因此可以在​​Activity或者Fragment​​中直接传递​​this​​。

在传递上下文给 ​​ViewModel​​之后,会通过​​ViewModelStore​​里面的​​HashMap​​去根据不同的​​key​​取出不同的​​ViewModel​​, 而这里的 ​​key​​是​​androidx.lifecycle.ViewModelProvider.DefaultKey:​​加上​​ViewModel​​类所在的包含路径的全名称字符串。

而​​Activity​​中的​​ViewModel​​,除非被杀死或者关闭,否则一直存在。 这也就是为什么​​Activity​​中的多个​​Fragment​​能共享​​ViewModel​​中的数据。

主要代码如下:

/**
* Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
* an activity), associated with this {@code ViewModelProvider}.
* <p>
* The created ViewModel is associated with the given scope and will be retained
* as long as the scope is alive (e.g. if it is an activity, until it is
* finished or process is killed).
*
* @param key The key to use to identify the ViewModel.
* @param modelClass The class of the ViewModel to create an instance of it if it is not
* present.
* @param <T> The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);

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.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

总结

​ViewModel​​的主要作用:

  • 以注重生命周期的方式存储和管理界面相关的数据
  • 让数据可在发生屏幕旋转等配置更改后继续留存
  • 让​​Activity​​下的所有​​Fragment​​可以共享ViewModel中的数据
  • 结合​​LiveData​​使用可避免内存泄漏