Android架構(gòu)組件(Architecture Components)之 ViewModel詳解

寫在前面

組件架構(gòu)三劍客終于來到了最后一篇:ViewModel蚣旱,關(guān)于LifecycleLiveData可以看之前的文章配紫。ViewModel和Lifecycle和LiveData的關(guān)聯(lián)并不大债鸡,可以單獨拿出來使用诱鞠。這里用的依賴主要是AndroidX里面的,其他版本可能有些不同镣丑,但核心邏輯應(yīng)該還是一致的宣谈。

implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01'

我們都知道,ViewModel的核心功能之一就是幫我們保存數(shù)據(jù)肄梨,特別是當(dāng)Activity重建時還能將數(shù)據(jù)保存下來阻荒。頁面重建保存數(shù)據(jù),看到這里我們可能很容易的就想到了onSaveInstanceState()方法峭范,我們可以在這里保存數(shù)據(jù)财松,以便Activity重新創(chuàng)建時在onCreate()方法中接收到保存下來的數(shù)據(jù)瘪贱。但它是有局限的纱控,只能保存一些簡單的數(shù)據(jù)或者一些經(jīng)過序列化的數(shù)據(jù),并且數(shù)據(jù)量還不能太大菜秦。ViewModel的出現(xiàn)甜害,正好彌補了這一不足。

ViewModel結(jié)構(gòu)與Lifecycle的結(jié)構(gòu)類似球昨,Activity/Fragment除了實現(xiàn)一個LifecycleOwner接口外尔店,還實現(xiàn)了一個ViewModelStoreOwner接口,它只有一個方法用來獲取ViewModelStore,然后通過ViewModelStore來負責(zé)添加和移除ViewModel嚣州。

viewmodel.png

ViewModelProvider

ViewModel的創(chuàng)建是ViewModelProvider提供的鲫售。要看ViewModel是怎么被創(chuàng)建并添加進ViewModelStore的,還得先從我們最熟悉的api入手该肴。

// 這里的this是指Activity
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

先創(chuàng)建一個ViewModelProvider情竹,再通過它來獲取ViewModel。

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    this(store, new FactoryWrapper(factory));
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull KeyedFactory factory) {
    // 要注意這里的mFactory是KeyedFactory
    mFactory = factory;
    mViewModelStore = store;
}

Factory

這個方法的目的是要構(gòu)造一個ViewModelProvider匀哄,先來講講工廠Factory秦效,ViewModelStore
放到后面講。默認我們沒有傳工廠進來涎嚼,這里會幫我們構(gòu)建一個AndroidViewModelFactory對象阱州。因為這里面出現(xiàn)了很多Factory相關(guān)的類,所以我覺得還是有必要先將Factory的結(jié)構(gòu)講一下法梯,有助于了解苔货。

factory.png

結(jié)合上面的代碼和類圖來看,這個框架會默認給我們提供一個AndroidViewModelFactory工廠對象立哑,然后又將它封裝成了一個KeyedFactory對象蒲赂,再加上ViewModelStore對象,一起構(gòu)造出了ViewModelProvider刁憋。短短的一個方法里面出現(xiàn)了工廠模式(Factory)和裝飾器模式(FactoryWrapper)滥嘴,真的是很佩服。

ViewModel

拿到ViewModelProvider對象后至耻,再來看它的get方法:

@NonNull
@MainThread
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);
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // mFactory指的是FactoryWrapper
    viewModel = mFactory.create(key, modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

首先是根據(jù)class獲取一個key值若皱,然后先從mViewModelStore中查詢有沒有相應(yīng)的ViewModel,當(dāng)然我們第一次調(diào)用肯定是拿不到的尘颓,需要走下面的創(chuàng)建步驟,創(chuàng)建完再添加到ViewModelStore中去走触。

private static class FactoryWrapper implements KeyedFactory {
    private final Factory mFactory;

    FactoryWrapper(Factory factory) {
        mFactory = factory;
    }

    @Override
    public <T extends ViewModel> T create(String key, Class<T> modelClass) {
        // mFactory指的是AndroidViewModelFactory
        return mFactory.create(modelClass);
    }
}

很奇怪的是這里的key并沒有被用到,不知道后面會不會添加一些新的東西進來疤苹。

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.getConstructor(Application.class).newInstance(mApplication);
        } catch (...) {
            // 一些catch
        }
    }
    return super.create(modelClass);
}

如果這個ViewModel是繼承自AndroidViewModel的話互广,就用AndroidViewModelFactory創(chuàng)建一個,否則卧土,用NewInstanceFactory創(chuàng)建一個普通的ViewModel惫皱。

Android給的建議是不要在ViewModel中使用到任何與Android相關(guān)的代碼,最簡單的檢查辦法就是看有沒有import進*.android.*相關(guān)的東西尤莺。但是這又有點不現(xiàn)實旅敷,因為我們經(jīng)常需要用到Context去獲取一些資源。為了解決這個問題颤霎,Android就給我們提供了AndroidViewModel媳谁。所以如果有這個需求的話可以讓你的ViewModel繼承AndroidViewModel涂滴。
另外,從ViewModel的創(chuàng)建過程來看晴音,如果我們需要在構(gòu)造函數(shù)里傳一些別的參數(shù)的話柔纵,就需要自己去構(gòu)建工廠類了。

講完了Factory锤躁,再回過頭來看ViewModelStore首量,Activity/Fragment創(chuàng)建ViewModel的過程唯一的區(qū)別就在于ViewModelStore獲取方式的不同。

activity.getViewModelStore();
fragment.getViewModelStore();

Activity.getViewModelStore()

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

首次調(diào)用這個方法进苍,肯定是通過new方法拿到的加缘,可以猜出ViewModelStore的緩存就是通過NonConfigurationInstances緩存下來的。而它又是在onRetainNonConfigurationInstance()方法中保存下來的觉啊,然后使用getLastNonConfigurationInstance()獲取出來的拣宏。這個與onSaveInstanceState()onRestoreInstanceState()是類似的。

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

關(guān)于onRetainNonConfigurationInstance()getLastNonConfigurationInstance()這兩個方法可以來Activity中看一下具體的流程杠人。首先狀態(tài)得保存下來勋乾,才能在重建的時候取出來。既然是要保存狀態(tài)嗡善,那肯定是在onDestroy()的時候保存的辑莫,所以直接來看看ActivityThread$performDestroyActivity()

// ActivityThread.java
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    if (r != null) {
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        // 如果還沒調(diào)用onPause()的話,調(diào)用onPause()
        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            // 調(diào)用onStop()
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                // 劃重點罩引,保存狀態(tài)
                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);
                }
            }
        }
        try {
            r.activity.mCalled = false;
            // 調(diào)用onDestroy()
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + safeToComponentShortString(r.intent) +
                    " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException(
                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
                        + ": " + e.toString(), e);
            }
        }
        r.setState(ON_DESTROY);
    }
    mActivities.remove(token);
    StrictMode.decrementExpectedActivityCount(activityClass);
    return r;
}

再繼續(xù)跟到Activity里面

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

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

第一行就是調(diào)用onRetainNonConfigurationInstance()來保存Activity的狀態(tài)各吨。這個方法返回的NonConfigurationInstances對象保存在ActivityClientRecord中,然后在重啟Activity的onAttach()方法中將NonConfigurationInstances拿回來袁铐,從而實現(xiàn)了數(shù)據(jù)的不丟失揭蜒。

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) {
    
    ......
    
    // 取回NonConfigurationInstances
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
}

Fragment.getViewModelStore()

// Fragment.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    return mFragmentManager.getViewModelStore(this);
}

// FragmentManagerImpl.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    return mNonConfig.getViewModelStore(f);
}

// FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

可以看到,最后是通過FragmentManagerViewModel來保存ViewModelStore的剔桨,key是相應(yīng)的Fragment的ID屉更。哦對了,F(xiàn)ragmentManagerViewModel自己也是一個ViewModel洒缀。

Fragment的數(shù)據(jù)恢復(fù)是在FragmentActivity$onCreate()里面做的瑰谜。

@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 在這里恢復(fù)數(shù)據(jù)
    mFragments.attachHost(null /*parent*/);

    super.onCreate(savedInstanceState);
    
    ......
}

最終走到FragmentManagerImpl中去:

public void attachController(@NonNull FragmentHostCallback host,
                             @NonNull FragmentContainer container, @Nullable Fragment parent) {
    if (mHost != null) throw new IllegalStateException("Already attached");
    mHost = host;
    mContainer = container;
    mParent = parent;
    if (parent != null) {
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) {
        // 這里是通過Activity的getViewModelStore()獲取ViewModelStore的
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
}

在這里初始化mNonConfig,所以我們才能通過mNonConfig.getViewModelStore()去獲取ViewModelStore树绩。重新創(chuàng)建Activity的時候萨脑,是通過Activity的getViewModelStore()去拿到ViewModelStore的,再通過它去拿到FragmentManagerViewModel葱峡。這里感覺有點繞砚哗×可以這么說吧砰奕,F(xiàn)ragment的ViewModelStore是存放到FragmentManagerViewModel中的蛛芥,然后FragmentManagerViewModel又被放到了Activity的ViewModelStore中,Activity在保存數(shù)據(jù)的時候自然也就將FragmentManagerViewModel保存了下來军援,從而將Fragment的ViewModelStore保存了下來仅淑。

另外,網(wǎng)上有一些文章說Fragment的ViewModelStore是通過Fragment$setRetainInstance()來保存的胸哥,可能是引用的版本號不一樣吧涯竟,畢竟Android還在繼續(xù)更新中。我這里通過調(diào)試發(fā)現(xiàn)是以這種方式保存的空厌÷可能以后版本不一樣又會有所改動了。

最后

到這里嘲更,Lifecycle, LiveData和ViewModel的原理筐钟,總算是解析完了。其中至少是用到了觀察者模式赋朦,工廠模式篓冲,裝飾器模式,自己分析完之后還是有所收獲的宠哄。也可以看到ViewModel的原理和Activity的生命周期最加緊密壹将,先給自己挖個坑,后面找時間再來寫一篇Activity的啟動流程的毛嫉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诽俯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子承粤,更是在濱河造成了極大的恐慌惊畏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件密任,死亡現(xiàn)場離奇詭異颜启,居然都是意外死亡,警方通過查閱死者的電腦和手機浪讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門缰盏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淹遵,你說我怎么就攤上這事口猜。” “怎么了透揣?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵济炎,是天一觀的道長。 經(jīng)常有香客問我辐真,道長须尚,這世上最難降的妖魔是什么崖堤? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮耐床,結(jié)果婚禮上密幔,老公的妹妹穿的比我還像新娘。我一直安慰自己撩轰,他們只是感情好胯甩,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堪嫂,像睡著了一般偎箫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皆串,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天镜廉,我揣著相機與錄音,去河邊找鬼愚战。 笑死娇唯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寂玲。 我是一名探鬼主播塔插,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拓哟!你這毒婦竟也來了想许?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤断序,失蹤者是張志新(化名)和其女友劉穎流纹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體违诗,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡漱凝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诸迟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茸炒。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖阵苇,靈堂內(nèi)的尸體忽然破棺而出壁公,到底是詐尸還是另有隱情,我是刑警寧澤绅项,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布紊册,位于F島的核電站,受9級特大地震影響快耿,放射性物質(zhì)發(fā)生泄漏囊陡。R本人自食惡果不足惜芳绩,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望关斜。 院中可真熱鬧示括,春花似錦铺浇、人聲如沸痢畜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丁稀。三九已至,卻和暖如春倚聚,著一層夾襖步出監(jiān)牢的瞬間线衫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工惑折, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留授账,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓惨驶,卻偏偏與公主長得像白热,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粗卜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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