ViewModel源碼探究

一. 什么是ViewModel

官方對ViewModel的定義:

1、類職責:負責為界面準備數(shù)據(jù)(意味著一切處理數(shù)據(jù)邏輯的業(yè)務代碼钾唬,應該寫在ViewModel中)
2、在配置更改期間會自動保留ViewModel對象:因此可以作為跨頁面(Fragment)通訊的基石

二. ViewModel有什么優(yōu)點

  1. Activity配置更改重建時(比如屏幕旋轉(zhuǎn))保留數(shù)據(jù)
  2. UI組件(Activity與Fragment、Fragment與Fragment)間實現(xiàn)數(shù)據(jù)共享

對于第一條不用VM的情況下只能通過onSaveInstanceState保存數(shù)據(jù)抡秆,當activity重建后再通過onCreate或onRestoreInstanceState方法的bundle中取出奕巍,但如果數(shù)據(jù)量較大,數(shù)據(jù)的序列化和反序列化將產(chǎn)生一定的性能開銷儒士。
對于第二條如果不用VM的止,各個UI組件都要持有共享數(shù)據(jù)的引用,這會帶來兩個麻煩着撩,第一诅福,如果新增了共享數(shù)據(jù),各個UI組件需要再次聲明并初始化新增的共享數(shù)據(jù)拖叙;第二氓润,某個UI組件對共享數(shù)據(jù)修改,無法直接通知其他UI組件薯鳍,需手動實現(xiàn)觀察者模式咖气。而VM結(jié)合LiveData就可以很輕松的實現(xiàn)這一點。

知道了它的優(yōu)化挖滤,下面我們來看看如果使用它

三. 如何使用

首先寫個類崩溪,繼承 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關(guān)鍵字方便實現(xiàn)

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

activityViewModels是什么呢?如果Activity中有好幾個Fragment斩松,那么這幾個Fragment通過activityViewModels方法拿到的ViewModel都是同一樣伶唯。這樣就實現(xiàn)了數(shù)據(jù)的共享

當然viewModels和activityViewModels也是通過ViewModelProvider(this).get方法創(chuàng)建的。所以我們看源碼惧盹,也是從ViewModelProvider(this).get方法看是如果生成對象的乳幸。通過源碼就可以清楚的知道為什么它會有那兩個優(yōu)點了

3調(diào)用源碼分析

構(gòu)造方法

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

ViewModelProvider的構(gòu)造方法傳入的是一個ViewModelStoreOwner,獲取的是owner.getViewModelStore()方法岭参,getViewModelStore返回的是一個ViewModelStore對象反惕。而ViewModelStore對象就比較簡單,里面就主要有個Map<String,ViewModel>存儲ViewModel演侯。
看到這里可以猜測,我們獲取的ViewModel存儲應該就是在這個ViewModelStore里面背亥,類似于緩存一樣

ViewModelProvider(this)方法傳的是當前Activity對象秒际,那么肯定是哪個父類繼承了ViewModelStoreOwner接口。往上找發(fā)現(xiàn)ComponentActivity實現(xiàn)了這個接口

在ComponentActivity里面有個mViewModelStore

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

里面實現(xiàn)了getViewModelStore方法狡汉,這里是個關(guān)鍵點娄徊,可以看出來,如果mViewModelStore 為null盾戴,則去NonConfigurationInstances 里面拿寄锐,那這個getLastNonConfigurationInstance從哪里拿的?這里我們暫且不跟,先看它是如果創(chuàng)建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;
}

繼續(xù)看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,繼續(xù)調(diào)用重載方法盆顾,從前面mViewModelStore里面拿出來一個ViewModel怠褐,如果不為空,則直接返回您宪,可見mViewModelStore確實就是緩存用的奈懒,如果為空。則通過mFactory 創(chuàng)建一個宪巨。并且放入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創(chuàng)建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邏輯也簡單,也就是直接反射生成一個對象天吓。

分析到這我們發(fā)現(xiàn)贿肩,ViewModelProvider.get方法其實邏輯就是反射生成一個ViewMode,并存儲到當前Activity的mViewModelStore中,
那么問題來了龄寞,既然旋轉(zhuǎn)屏幕汰规,Activity會重新創(chuàng)建,那么它里面的變量mViewModelStore怎么還能保持是同一個物邑?

答案就在剛剛的ComponentActivity#getViewModelStore方法中溜哮,繼續(xù)看這個方法,這里的mViewModelStore并不是重新new色解,而是從一個NonConfigurationInstances 對象中去拿茂嗓,而這個getLastNonConfigurationInstance方法那就是這個問題的關(guān)鍵所在了

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

我們繼續(xù)看這個
getLastNonConfigurationInstance方法
按ctrl鍵點進去,進了Activity里面了

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

我們關(guān)心的是這個對象是在哪里賦值的科阎,在attach方法里面發(fā)現(xiàn)了賦值的地方

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

那么attach方法里面的這個lastNonConfigurationInstances又是從哪里來的呢述吸?
在ActivityThread里面的performLaunchActivity方法,創(chuàng)建時會調(diào)用這個方法綁定一些重要的數(shù)據(jù)到activity锣笨,
現(xiàn)在我們知道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方法中我們看到了賦值操作错英。如果發(fā)生了配置變化入撒,會調(diào)用activity的retainNonConfigurationInstances方法,存入ActivityClientRecord中

   ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
         ...
        if (getNonConfigInstance) {
            try {
              //如果發(fā)生了配置改變椭岩,數(shù)據(jù)存起來
                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;
}

似乎離真相越來越近了茅逮,璃赡,我們繼續(xù)看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;
}

里面回調(diào)了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;
}

至此,總結(jié)下流程:
當Activity配置發(fā)生了變化惩琉,執(zhí)行了ActivityThread#performDestroyActivity方法時豆励,會把ViewModelStore存儲在NonConfigurationInstances ,然后把NonConfigurationInstances 存儲在ActivityClientRecord瞒渠。
然后重啟Activity時良蒸,會在attach方法,把ActivityClientRecord的NonConfigurationInstances給Activity

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伍玖,一起剝皮案震驚了整個濱河市嫩痰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌窍箍,老刑警劉巖串纺,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異椰棘,居然都是意外死亡纺棺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門邪狞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祷蝌,“玉大人,你說我怎么就攤上這事帆卓【揠” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵剑令,是天一觀的道長糊啡。 經(jīng)常有香客問我,道長吁津,這世上最難降的妖魔是什么棚蓄? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮碍脏,結(jié)果婚禮上癣疟,老公的妹妹穿的比我還像新娘。我一直安慰自己潮酒,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布邪蛔。 她就那樣靜靜地躺著急黎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勃教,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天淤击,我揣著相機與錄音,去河邊找鬼故源。 笑死污抬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绳军。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼敲茄!你這毒婦竟也來了娃善?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤奶是,失蹤者是張志新(化名)和其女友劉穎楣责,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂沙,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秆麸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了及汉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮趣。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖豁生,靈堂內(nèi)的尸體忽然破棺而出兔毒,到底是詐尸還是另有隱情,我是刑警寧澤甸箱,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布育叁,位于F島的核電站,受9級特大地震影響芍殖,放射性物質(zhì)發(fā)生泄漏豪嗽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一豌骏、第九天 我趴在偏房一處隱蔽的房頂上張望龟梦。 院中可真熱鬧,春花似錦窃躲、人聲如沸计贰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躁倒。三九已至荞怒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秧秉,已是汗流浹背褐桌。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留象迎,地道東北人荧嵌。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像砾淌,于是被迫代替她去往敵國和親啦撮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容