ViewModel 使用及原理

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关数据。ViewModel 类可让数据在发生屏幕旋转等配置更改后继续留存,还可以避免异步请求后操作数据造成的内存泄漏

ViewModel 的使用

首先,引入 ViewModel:

 def lifecycle_version = "2.4.0-alpha02"
 // ViewModel
 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

然后定义一个数据类

public class UserInfo {

    private String firstName;
    private String lastName;
    private int age;

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

接着定义一个类,继承 ViewModel,并包含 UserInfo 属性

public class ViewModelInfo extends ViewModel {

    // 这个是 LiveData,用来监听数据改变的,一般会习惯把 LiveData 和 ViewModel 绑定使用,LiveData 我们后续再说
    private MutableLiveData<UserInfo> user;

    public MutableLiveData<UserInfo> getUser() {
        if (user == null) {
            user = new MutableLiveData<UserInfo>();
            loadUser();
        }
        return user;
    }

    private void loadUser() {

        UserInfo userInfo = new UserInfo();
        userInfo.setFirstName("firstName");
        userInfo.setLastName("lastName");
        user.setValue(userInfo);
    }

}

接下来我们就能在 Activity 中使用它了

//获取ViewModel对象
ViewModulInfo viewModulInfo = new ViewModelProvider(this).get(ViewModulInfo.class);

使用就是这么简单

对于 ViewModel ,只要 new ViewModelProvider(this) 中这个 this 是同一个 ViewModelStoreOwner,那么返回的 ViewModel 对象就是同一个。基于这个特性,我们就可以在 Activity 和 Fragment 或 Fragment 和Fragment 中传递数据。原理我们在下面的文章中分析

原理

1、创建 ViewModel

我们先看一下 new ViewModelProvider(this) 做了什么

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

首先 owner.getViewModelStore() 返回一个 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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            // 看看之前有没有,有就取缓存,没有则新建
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

ViewModelStore 是一个通过 HashMap 保存 ViewModel 的类:

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

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

对于第二个参数,我们知道,AppCompatActivity 继承自 FragmentActivity,FragmentActivity 继承自 ComponentActivity ,我们看下 ComponentActivity 的代码:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ...
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        ... {
         ...
         
  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.");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }  
           
         ...
  }

public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {
  private final Application mApplication;
  private final ViewModelProvider.Factory mFactory;
  private final Bundle mDefaultArgs;
  ...
    
   public SavedStateViewModelFactory(@Nullable Application application,
            @NonNull SavedStateRegistryOwner owner,
            @Nullable Bundle defaultArgs) {
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        mFactory = application != null
                ? ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                : ViewModelProvider.NewInstanceFactory.getInstance();
    } 
  ...  
  
}

可见,ComponentActivity 实现了 HasDefaultViewModelProviderFactory 接口,且重写了 getDefaultViewModelProviderFactory 方法,在该方法中返回了一个 ViewModelProvider.Factory 的实例 SavedStateViewModelFactory 。在 SavedStateViewModelFactory 中,将 mFactory 初始化为 AndroidViewModelFactory。

至于 NewInstanceFactory.getInstance(),它是一个简单工厂类,我们也看一下

public static class NewInstanceFactory implements Factory {

        private static NewInstanceFactory sInstance;

        /**
         * Retrieve a singleton instance of NewInstanceFactory.
         *
         * @return A valid {@link NewInstanceFactory}
         */
        @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) {
            //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);
            }
        }
    }

好了,现在我们知道,通过 new ViewModelProvider(this) 方法,ViewModelProvider 将 mFactory 初始化为 SavedStateViewModelFactory 对象,将 mViewModelStore 初始化为 ViewModelStore 对象。

接下来,再去看看 get(ViewModelInfo.class) 又做了哪些事情

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

本地类或者匿名类的时候 getCanonicalName() 方法将会返回null,也就是说不能使用这些类创建 ViewModel(这里留个问题,为什么不行?),接着,我们再看看 return 的 那个 get() 方法里,又做了哪些操作

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //看有没有缓存,有缓存就使用缓存
        ViewModel viewModel = mViewModelStore.get(key);

        //通过 isInstance 判断 modelClass 能不能强转为 viewModel 的类型
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            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;
    }

我们先看 mViewModelStore.get(key) ,这里通过 canonicalName 获取 mViewModelStore 中对应的 ViewModel,所以,通过 new ViewModelProvider(this) 创建的 ViewModelProvider,只要 “this” 相同,即是同一个 ViewModelStoreOwner ,那么相同的类就会有相同的 ViewModel 实例

如果缓存中有,则直接返回这个 ViewModel,并且判断如果 mFactory 是 OnRequeryFactory 的子类,则调用 onRequery 方法。通过上面的分析,我们知道 mFactory 就是 SavedStateViewModelFactory ,SavedStateViewModelFactory 继承于 ViewModelProvider.KeyedFactory ,而 ViewModelProvider.KeyedFactory 就是继承于 OnRequeryFactory。所以。这里如果缓存中有 ViewModel ,那就会执行 onRequery 方法,这个我们后续再分析。

如果缓存中没有,则新建一个并放到 mViewModelStore 里面。那怎么新建的呢?去 SavedStateViewModelFactory 类里看一下

private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
            SavedStateHandle.class};
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
        // isAssignableFrom:当前Class对象如果是参数Class对象的父类,父接口,或者是相同,都会返回true
        // 所以,这里就是判断 modelClass 是否是 AndroidViewModel 或是 AndroidViewModel 的子类
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel && mApplication != null) {
            // 查找构造器
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }

        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
            T viewmodel;
            if (isAndroidViewModel && mApplication != null) {
                // 通过反射创建对象
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            // 设置 tag
            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());
        }
    }

private static <T> Constructor<T> findMatchingConstructor(Class<T> modelClass,
            Class<?>[] signature) {
        for (Constructor<?> constructor : modelClass.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (Arrays.equals(signature, parameterTypes)) {
                return (Constructor<T>) constructor;
            }
        }
        return null;
    }

<T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            // It is possible that we'll call close() multiple times on the same object, but
            // Closeable interface requires close method to be idempotent:
            // "if the stream is already closed then invoking this method has no effect." (c)
            closeWithRuntimeException(result);
        }
        return result;
    }

在 SavedStateViewModelFactory 通过反射的方式创建了一个 ViewModel 对象。

销毁 ViewModel

分析到这,并没有发现 ViewModel 跟 Activity 的生命周期有什么关系,所以,想着在 ComponentActivity 中搜索一下 onDestroy 方法看看,结果尴尬了,ComponentActivity 中就没有重写 onDestroy 方法...

往前看看 FragmentActivity 啦,AppCompatActivity 啦,它们的 onDestroy 方法跟 ViewModel 也没有什么关系。

线索到这突然就断了,忽然灵光一闪,想起了分析 Lifecycle 源码时好像在 ComponentActivity 的构造函数中看到过一点不一样的东西,去找找

public ComponentActivity() {
        ...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // 清除 ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
        ...
    }

找到啦,可以看到,在非配置改变导致执行 Lifecycle.Event.ON_DESTROY 的时候,会清除掉 ViewModelStore,清除的方法我们也看一下

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    ...
     
    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

Activity 异常重建情况下 ViewModel 的保存和恢复

因为配置改变等原因造成的异常销毁呢,ViewModel是怎么保存及恢复的?这才是我们想知道的啊。我们直接从 AMS 去找,我们知道,Activity 配置发生变化的时候,会调用 ActivityThread 的 performDestroyActivity() 方法来销毁旧的 Activity,如下:

void performDestroyActivity(ActivityClientRecord r, boolean finishing,
              ...         
          if (getNonConfigInstance) {
              try {
                  r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
              } catch (Exception e) {
                  if (!mInstrumentation.onException(r.activity, e)) {
                      throw new RuntimeException("Unable to retain activity "
                             + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                  }
              }
          }
              ...
      }

我们看到配置改变了之后,会调用 activity.retainNonConfigurationInstances() 方法,将其返回的数据存储到 ActivityClientRecord 的 lastNonConfigurationInstances 中,我们跟进 retainNonConfigurationInstances() 看下:

NonConfigurationInstances retainNonConfigurationInstances() {
        // 保存数据
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        // We're already stopped but we've been asked to retain.
        // Our fragments are taken care of but we need to mark the loaders for retention.
        // In order to do this correctly we need to restart the loaders first before
        // handing them off to the next activity.
        mFragments.doLoaderStart();
        mFragments.doLoaderStop(true);
        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();

        if (activity == null && children == null && fragments == null && loaders == null
                && mVoiceInteractor == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }

接着,我们去 ComponentActivity 中的 onRetainNonConfigurationInstance 方法里,看看又做了哪些操作

 public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        // 保存当前的 ViewModelStore
        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;
    }

可以看到,在这个方法里,我们将 ViewModelStore 保存起来。所以,先总结一下:在 Activity 配置发生改变导致重建的时候,我们通过 ActivityThread 的 performDestroyActivity() ,调用 Activity 的 retainNonConfigurationInstances(),在 retainNonConfigurationInstances() 方法中,通过调用 ComponentActivity 的 onRetainNonConfigurationInstance() 将 ViewModelStore 保存下来,然后在 ActivityThread 的 performDestroyActivity() ,将其存储到到 ActivityClientRecord 的 lastNonConfigurationInstances 属性中,这就是存储的逻辑

那恢复的逻辑又是什么呢?我们继续往下看。根据 Activity 的 onRetainNonConfigurationInstance() 方法的注释来看,恢复跟 getLastNonConfigurationInstance() 方法有关

 public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

这个 mLastNonConfigurationInstances 是在哪赋值的呢?是在 attach 的时候。

NonConfigurationInstances mLastNonConfigurationInstances;

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...
    }

走,去 ActivityThread 中的 performLaunchActivity 方法中看看 attach 的参数是咋来的吧

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         ...
         activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         //主要的就是这个 r.lastNonConfigurationInstances
                         r.embeddedID, r.lastNonConfigurationInstances, config,
                         r.referrer, r.voiceInteractor, window, r.configCallback,
                         r.assistToken, r.shareableActivityToken);
  
         ...
  
          return activity;
}
 

可以看到,在调用 attach() 方法的时候,将之前在 ActivityClientRecord 类的 lastNonConfigurationInstances 属性中存储的数据又带回给了 Activity,这样,在调用 getLastNonConfigurationInstance() 时获取到的就是之前保存的数据,这就是恢复的逻辑

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容