ViewModel源码探究

一. 什么是ViewModel

官方对ViewModel的定义:

1、类职责:负责为界面准备数据(意味着一切处理数据逻辑的业务代码,应该写在ViewModel中)
2、在配置更改期间会自动保留ViewModel对象:因此可以作为跨页面(Fragment)通讯的基石

二. ViewModel有什么优点

  1. Activity配置更改重建时(比如屏幕旋转)保留数据
  2. UI组件(Activity与Fragment、Fragment与Fragment)间实现数据共享

对于第一条不用VM的情况下只能通过onSaveInstanceState保存数据,当activity重建后再通过onCreate或onRestoreInstanceState方法的bundle中取出,但如果数据量较大,数据的序列化和反序列化将产生一定的性能开销。
对于第二条如果不用VM,各个UI组件都要持有共享数据的引用,这会带来两个麻烦,第一,如果新增了共享数据,各个UI组件需要再次声明并初始化新增的共享数据;第二,某个UI组件对共享数据修改,无法直接通知其他UI组件,需手动实现观察者模式。而VM结合LiveData就可以很轻松的实现这一点。

知道了它的优化,下面我们来看看如果使用它

三. 如何使用

首先写个类,继承 ViewModel

  class UserViewModel : ViewModel() {
      private var users: MutableLiveData<String>? = null
      fun getUsers(): LiveData<String>? {
        if (users == null) {
            users = MutableLiveData<String>()
            users?.value = "hello world"
      }
      return users
  }
}

然后后Activity里面使用

class ViewModelActivity : AppCompatActivity() {
    lateinit var viewmodel: MyViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
         setTitle("ViewModel的使用")
        //通过ViewModelProvider获取实例
        viewModel =  ViewModelProvider(this).get(UserViewModel::class.java)
        viewModel.getUsers()?.observe(this, Observer {
            Log.d("ViewModel", it)
        })
    }
}

如果引入了依赖库

implementation "androidx.fragment:fragment-ktx:1.2.4"

那么上述实例化ViewModel可以通过by关键字方便实现

private val viewModel by viewModels<MyViewModel>()
// Activity中如果共享的ViewModel
private val viewModel by activityViewModels<MyViewModel>()

activityViewModels是什么呢?如果Activity中有好几个Fragment,那么这几个Fragment通过activityViewModels方法拿到的ViewModel都是同一样。这样就实现了数据的共享

当然viewModels和activityViewModels也是通过ViewModelProvider(this).get方法创建的。所以我们看源码,也是从ViewModelProvider(this).get方法看是如果生成对象的。通过源码就可以清楚的知道为什么它会有那两个优点了

3调用源码分析

构造方法

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

ViewModelProvider的构造方法传入的是一个ViewModelStoreOwner,获取的是owner.getViewModelStore()方法,getViewModelStore返回的是一个ViewModelStore对象。而ViewModelStore对象就比较简单,里面就主要有个Map<String,ViewModel>存储ViewModel。
看到这里可以猜测,我们获取的ViewModel存储应该就是在这个ViewModelStore里面,类似于缓存一样

ViewModelProvider(this)方法传的是当前Activity对象,那么肯定是哪个父类继承了ViewModelStoreOwner接口。往上找发现ComponentActivity实现了这个接口

在ComponentActivity里面有个mViewModelStore

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    LifecycleOwner,
    ViewModelStoreOwner,
   ...{
      private ViewModelStore mViewModelStore;
}

里面实现了getViewModelStore方法,这里是个关键点,可以看出来,如果mViewModelStore 为null,则去NonConfigurationInstances 里面拿,那这个getLastNonConfigurationInstance从哪里拿的?这里我们暂且不跟,先看它是如果创建ViewModel的,如果前面获取都为null,最后是直接new了一个出来

public ViewModelStore getViewModelStore() {
    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;
}

继续看ViewModelProvider#get方法

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

通过类名和前缀DEFAULT_KEY 生成一个字符串key,继续调用重载方法,从前面mViewModelStore里面拿出来一个ViewModel,如果不为空,则直接返回,可见mViewModelStore确实就是缓存用的,如果为空。则通过mFactory 创建一个。并且放入mViewModelStore

 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.
        }
    }
 //通过factory创建viewModel
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
  //存入mViewModelStore
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

这个代码也好理解,就是从mViewModelStore里面拿对象,拿不到就让factory去生成一个。由于我们没有传入factory,这里的factory就是默认的NewInstanceFactory。而NewInstanceFactory逻辑也简单,也就是直接反射生成一个对象。

分析到这我们发现,ViewModelProvider.get方法其实逻辑就是反射生成一个ViewMode,并存储到当前Activity的mViewModelStore中,
那么问题来了,既然旋转屏幕,Activity会重新创建,那么它里面的变量mViewModelStore怎么还能保持是同一个?

答案就在刚刚的ComponentActivity#getViewModelStore方法中,继续看这个方法,这里的mViewModelStore并不是重新new,而是从一个NonConfigurationInstances 对象中去拿,而这个getLastNonConfigurationInstance方法那就是这个问题的关键所在了

  public ViewModelStore getViewModelStore() {
    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;
}

我们继续看这个
getLastNonConfigurationInstance方法
按ctrl键点进去,进了Activity里面了

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

我们关心的是这个对象是在哪里赋值的,在attach方法里面发现了赋值的地方

 final void attach(...) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);
    ....
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

那么attach方法里面的这个lastNonConfigurationInstances又是从哪里来的呢?
在ActivityThread里面的performLaunchActivity方法,创建时会调用这个方法绑定一些重要的数据到activity,
现在我们知道lastNonConfigurationInstances是存在ActivityClientRecord中的。

   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.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback);

           ...

    return activity;
}

那么r.lastNonConfigurationInstances又是在哪里赋值的呢?
同样的
ActivityThread#performDestroyActivity方法中我们看到了赋值操作。如果发生了配置变化,会调用activity的retainNonConfigurationInstances方法,存入ActivityClientRecord中

   ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
         ...
        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);
                }
            }
        }
       ...
    return r;
}

似乎离真相越来越近了,,我们继续看retainNonConfigurationInstances方法
Activity#retainNonConfigurationInstances

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    ...
    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;
}

里面回调了onRetainNonConfigurationInstance方法,而ComponentActivity复写了这个方法,把viewModelStore 存入了NonConfigurationInstances

ComponentActivity#onRetainNonConfigurationInstance方法,把viewModelStore存在NonConfigurationInstances中

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

至此,总结下流程:
当Activity配置发生了变化,执行了ActivityThread#performDestroyActivity方法时,会把ViewModelStore存储在NonConfigurationInstances ,然后把NonConfigurationInstances 存储在ActivityClientRecord。
然后重启Activity时,会在attach方法,把ActivityClientRecord的NonConfigurationInstances给Activity

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

推荐阅读更多精彩内容