本文基于:

androidx.lifecycle:lifecycle-viewmodel:2.3.0

androidx.fragment:fragment:1.1.0

androidx.appcompat:appcompat:1.2.0

一.ViewModel简介

ViewModel是google随Jetpack发布的,旨在以注重生命周期的方式存储和管理界面相关的数据,ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。详细的文档介绍参考:

https://developer.android.com/topic/libraries/architecture/viewmodel 

二.Lifecycle的实现原理

1. 示例

这个例子定义了一个ViewModel,内部有一个定时器,计时值通过接口提供给Activity使用。

public class MyViewModel extends ViewModel {
    private static final String TAG = "MyViewModel";
    private Timer mTimer;
    private int mCurrentSecond;

    public void startTiming() {
         if (mTimer == null) {
            mCurrentSecond = 0;
            mTimer = new Timer();
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    mCurrentSecond++;
                    if (onTimeChangeListener != null) {
                        onTimeChangeListener.onTimeChanged(mCurrentSecond);
                    }
                }
          };
          mTimer.schedule(timerTask, 1000, 1000);
    }

    public interface OnTimeChangeListener {
        void onTimeChanged(int second);
    }

    private OnTimeChangeListener onTimeChangeListener;

    public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
        this.onTimeChangeListener = onTimeChangeListener;
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        Log.d(TAG, "onCleared()");
        mTimer.cancel();
    }
}

public class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("MyOldModel", "onCreate");
        setContentView(R.layout.viewmodel);
        final Button tvTime = findViewById(R.id.btn_demo_viewmodel);
        ViewModelProvider vmp = new ViewModelProvider(this,
               ViewModelProvider.AndroidViewModelFactory.getInstance(
                    getApplication()));
        MyViewModel vm = vmp.get(MyViewModel.class);
        //MyAndroidViewModel vm = vmp.get(MyAndroidViewModel.class);
        vm.setOnTimeChangeListener(second -> runOnUiThread(
             () -> tvTime.setText("TIME:" + second)));
        vm.startTiming();
    }
}

运行后,启动定时器,按钮上的数字显示定时器的次数,并且在转屏的时候数字是连续的,我们并没有屏蔽转屏的configrationChanged,所以转屏时Activity的生命周期重新执行,onCreate等方法会重新调用。

2. ViewModel的创建过程

代码中,通过这个方式创建:

ViewModelProvider vmp = new ViewModelProvider(this,
  ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()));
MyViewModel vm = vmp.get(MyViewModel.class);

   先创建一个ViewModelProvider,其参数有2个:

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

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

我们传入的第一个参数this是一个AppCompatActivity,其实现了接口ViewModelStoreOwner,这个接口只有一个方法getViewModelStore(),ViewModelProvider的构造方法只是把ViewModelStore和Factory存起来了。ViewModel是在后面调用get的时候才会构造,另外ViewModelStore在Activity初始化的时候会构建,getViewModelStore()获取的时候一般都可以直接使用,后面会详细分析。在继续跟踪代码之前,先把类图附上:

android viewmodel 处理网络错误 android viewmodel原理_转屏

有了ViewModelProvider后,继续看它的get方法:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    ......
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

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

创建过程如下:

1.从HashMap中获取: mViewModelStore.get(key),返回缓存的。

1.1.如果存在,且Factory是OnRequeryFactory的实例,将会调用其onRequery()方法再返回。也就是说对于连续get同一个ViewModel的时候,可以在其从HashMap缓存中返回前,执行一些操作(不过这块有个问题OnRequeryFactory和其子类KeyedFactory都是抽象类,并且都是package的访问权限,应用程序没法直接使用)。

1.2.如果存在,且Factory不是OnRequeryFactory的实例,直接返回。

2.如果不存在,判断Factory是否KeyedFactory的实例,是的话,调用create()的两个参数版本。

3.Factory不是KeyedFactory的实例,调用create()的一个参数版本。create()先判断是不是AndroidViewModel,如果是,查找带Application参数的构造函数并执行newInstance(),否则调用super的create(),即:NewInstanceFactory的create(),内部实现就是调用newInstance()。

4.创建后放到HashMap里,并返回。

注意,KeyedFactory是抽象类,也就是说3中的create(@NonNull String key, @NonNull Class<T> modelClass)需要自己实现,另外,ViewModel和AndroidViewModel的差别就是AndroidViewModel持有Application,注意它们的生命周期是一样的,在Activity退出后,其onCleared()都会被调用。

小结:get()的实现为什么用反射去构造ViewModel实例?直接new ViewModel不行吗?个人见解:

1.简化代码,如果不反射,当我们需要添加一个ViewModel时,也许需要这样做:

ViewModel vm = new MyViewModel();
ViewModeStore vms = this.getViewModeStore();
String key = vm.getCanonicalName() + ...
if (!vms.contains(key) {
   vms.put(vm)
}

2.方便的通过Factory注入构造对象前需要做的准备工作,例如覆写类KeyedFactory,或者OnRequeryFactory的onRequery()方法

3.ViewModel的子类只能调用默认构造函数,减少传入Context的机会,ViewModel的生命周期很长,如果传入的Context是Activity(这是程序员经常干的),传入的不是owner所属的Activity的话,就会导致持有别的Activity的引用,将会内存泄漏,对于一定要Context的,默认提供了AndroidViewModel,持有Application。当然MyViewModel是自定义的,不能阻止程序员通过set方法把Context传入。

3. ViewModel的生命周期

官方的一张图,可以看到ViewModel的生命周期是持续Activity的生命周期的,从ViewModel生成到Activity的销毁一直存在,旋转屏幕不会影响其生命周期。

android viewmodel 处理网络错误 android viewmodel原理_转屏_02

3.1 ViewModelStore的恢复

在configurationChange的时候,Activity默认会被系统销毁并重建,重建的过程中,可以快速恢复一些对象,在AcitityThread中,如下:

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ...
    if (r != null) {
        ...
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
               ...
            }
        }
        mInstrumentation.callActivityOnDestroy(r.activity);
        ...
    }
}

performDestroyActivity时系统会将不受configeration影响的对象存在ActivityClientRecord的lastNonConfigurationInstances中,后续重建Activity的时候会使用,这里不在列出代码。    可见,系统会调用Activity的retainNonConfigurationInstances以便快速的恢复某些对象,这个方法NonConfigurationInstances对象,里面封装所有的需要快速创建的内容,如下:

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

retainNonConfigurationInstances内部调用各个方式来收集其成员,比如调用onRetainNonConfigurationInstance收集Object activity的值,如下图:

android viewmodel 处理网络错误 android viewmodel原理_转屏_03

ComponentActivity会覆写onRetainNonConfigurationInstance:

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里的NonConfigurationInstances 和Activity里的NonConfigurationInstances 不一样,ComponentActivity里的只有两个字段:

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

可以看到onRetainNonConfigurationInstance里保存了ViewModelStore和一个custom的Object。在getViewModelStore()(这个方法在构造ViewmodelProvider时就会调用)的时候,会优先从lastNonConfigurationInstances获取,如果没有再创建,这样就保证configurationChange(比如转屏)后,ViewModelStore都是同一个对象,注意,转屏后,旧的Activity对象被销毁,并创建一个新的Activity对象,其成员变量mViewModelStore肯定是为null,所以getViewModelStore()需要从lastNonConfigurationInstances获取,不然,转屏后的mViewModelStore就是新的了。

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

3.2 onCleared的执行

onCleared的执行依赖LifeCycle,在ComponentActivity里注册一个监听,当Activity destroy的时候调用:

public ComponentActivity() {
    ...
    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();
                }
            }
        }
    });
}

注意这里会忽略configChange导致onDestroy,以便转屏的时候不会被清理,ViewModelStore的clear会调用每一个ViewModel的clear如下:

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

ViewModel的clear如下:

protected void onCleared() {
}

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的生命周期是独立的,从创建后,只在Activity destory后会被销毁。

三. Fragment里的ViewModel

在FragementManagerImpl中,会使用FragmentManagerViewModel,后者就是一个ViewModel,其ViewModelStore和Activity的是同一个,具体可以查看FragmentActivity的内部类HostCallbacks的getViewModelStore:

class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
        ViewModelStoreOwner,
        OnBackPressedDispatcherOwner {
    ...
    public ViewModelStore getViewModelStore() {
        return FragmentActivity.this.getViewModelStore();
    }
}

FragementManagerImpl中的attachController会使用它来初始化FragmentManagerViewModel,其中mNonConfig就是这个对象:

public void attachController(@NonNull FragmentHostCallback host,
        @NonNull FragmentContainer container, @Nullable final Fragment parent) {
    ... 
    // Get the FragmentManagerViewModel
    if (host instanceof ViewModelStoreOwner) {
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    }
}

static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
    ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore, FACTORY);
    return viewModelProvider.get(FragmentManagerViewModel.class);
}

相关的类关系如下:

android viewmodel 处理网络错误 android viewmodel原理_ide_04

FragmentManagerViewModel除了本身是个ViewModel,其还有两个HashMap和ViewModel相关:

(1)HashMap<String, ViewModelStore> mViewModelStores,存储每一个Fragment的ViewModelStroe,key是标识Fragment的String,也就是说每一个Fragment都有一套ViewModelStrore,可以增加多个ViewModel。

(2)HashMap<String, FragmentManagerViewModel> mChildNonConfigs,这个是给Fragment的子Fragment使用的,key是标识其父Fragment的String,Value是个独立的FragmentManagerViewModel。

有点类似View和ViewGroup,Activity中有FragementManagerViewModel,其里面的Fragment放到mViewModelStores中(类似View),如果某个Fragment有子Fragment,那它会有个单独的FragementManagerViewModel对象进行管理(类似ViewGroup)

四. ViewModel的作用

(1)configChange后,数据不会丢失

从前面的分析可以看到,当界面发生横竖屏切换时,数据不会丢失。

(2)Fragment之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。

可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信。

(3)担任MVVM中的VM角色

MVVM中的VM即是ViewModel,作为View和Model之间的连接桥梁,配合LiveData,可以轻松实现观察者模式。