Jetpack學(xué)習(xí)之----ViewModel

一旗唁、介紹

官方學(xué)習(xí)文檔

ViewModel就是存儲(chǔ)頁(yè)面相關(guān)的數(shù)據(jù)饼煞,并將這些數(shù)據(jù)和Activity身腻、Fragment等有生命周期相關(guān)的組件相關(guān)聯(lián),賦予數(shù)據(jù)生命周期厘熟。

特點(diǎn):

  1. 將數(shù)據(jù)與界面控制器進(jìn)行分離(也就是將經(jīng)常在Actvity偎血、Fragment中的保存的數(shù)據(jù)分離出來(lái)诸衔,這樣這樣界面控制器就主要負(fù)責(zé)與UI相關(guān)的事情即可)

  2. 加大保存數(shù)據(jù)的范圍(當(dāng)頁(yè)面發(fā)送無(wú)意關(guān)閉時(shí)盯漂,我們一般是onSaveInstanceState()來(lái)保存數(shù)據(jù)颇玷,但是這樣保存的數(shù)據(jù)大小有限制,而且是必須可序列化再反序列化的數(shù)據(jù)就缆,而viewmodel可以保存我們頁(yè)面所需的所有數(shù)據(jù))

  3. 分離界面控制器的工作(將一些與數(shù)據(jù)相關(guān)的業(yè)務(wù)處理分來(lái)出來(lái)帖渠,減少控制器對(duì)業(yè)務(wù)的處理工作)

  4. ViewModel 保存的數(shù)據(jù)會(huì)在activity完成時(shí),由框架調(diào)用onCleared方法清理資源

ViewModel的生命周期

在viewModel對(duì)象創(chuàng)建時(shí)開(kāi)始竭宰,一直到他所關(guān)聯(lián)的界面控制器銷毀時(shí)才銷毀空郊,這就說(shuō)明了即使發(fā)生了橫豎屏切換,界面相關(guān)的數(shù)據(jù)也是一直存在并且不受橫豎屏切換的影響切揭。

通常我們是在Actvity的onCreate()方法中來(lái)創(chuàng)建ViewModel對(duì)象狞甚,該ViewModel對(duì)象會(huì)一直在內(nèi)存中,直到這個(gè)Activity銷毀時(shí)才釋放資源廓旬。

二哼审、使用

1、 添加依賴

// ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha03"

2孕豹、 創(chuàng)建viewmodel

class MainViewModel:ViewModel() {
    var name:String="Leon"
    var position:MutableLiveData<String> = MutableLiveData()
}

3涩盾、 簡(jiǎn)單使用

class MainActivity : AppCompatActivity() {

    val TAG=MainActivity::class.java.simpleName
    lateinit var viewModel:MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel= ViewModelProvider(this).get(MainViewModel::class.java)
        
        //下面是結(jié)合LiveData來(lái)使用ViewModel的數(shù)據(jù)的
        viewModel.position.observe(this,{
            Log.e(TAG, "onCreate: position value = ${it}")
        })
    }
}

三、工作原理

原理:

當(dāng)開(kāi)始創(chuàng)建Activity的實(shí)例對(duì)象時(shí)励背,會(huì)生成用來(lái)存儲(chǔ)ViewModel對(duì)象的ViewModelStore實(shí)例(使用Hashmap數(shù)據(jù)結(jié)構(gòu)存儲(chǔ))春霍,并給當(dāng)前Activity的生命周期添加觀察者,用于觀察Activity的生命周期變化叶眉,當(dāng)Activity的生命周期是ON_DESTROY時(shí)址儒,就會(huì)清理掉ViewModelStore中存儲(chǔ)的ViewModel的所有對(duì)象,釋放資源衅疙。

創(chuàng)建ViewModelProvider對(duì)象實(shí)例時(shí)莲趣,會(huì)在其構(gòu)造中調(diào)用生成一個(gè)AndroidViewModelFactory的全局工程對(duì)象,我們會(huì)使用這個(gè)工廠對(duì)象來(lái)反射自定義的ViewModel對(duì)象炼蛤。

當(dāng)使用ViewModelProvider對(duì)象實(shí)例get自定義的ViewModel對(duì)象時(shí)妖爷,會(huì)先從ViewModelStore的Hashmap去找,如果沒(méi)有找到理朋,就用上一步的AndroidViewModelFactory實(shí)例來(lái)反射自定義的ViewModel對(duì)象絮识,并將該ViewModel保存到Hashmap中,下次使用的時(shí)候就可以直接使用嗽上;如果找了ViewModel 就直接返回該ViewModel的對(duì)象次舌。

從上面ViewModel的工作原理可以得知:

1、ViewModel 一旦創(chuàng)建好了兽愤,就會(huì)一直保存到當(dāng)前界面控制器(Activity 彼念、Fragment等)銷毀時(shí)才會(huì)釋放資源挪圾;

2、不同的界面控制器逐沙,ViewModel 的對(duì)象時(shí)存在不同的Hashmap中的哲思,他們也是不同的對(duì)象;局部單例吩案;

3棚赔、要做到全局單例ViewModel對(duì)象,可以將ViewModel放到Application中去徘郭;

接下來(lái)從源碼角度來(lái)分析一下原理:

1靠益、創(chuàng)建存儲(chǔ)ViewModel的容器ViewModelStore對(duì)象

在構(gòu)建Activity的對(duì)象時(shí),在其父類ComponentActivity.java中實(shí)現(xiàn)了接口ViewModelStoreOwner残揉,在其實(shí)現(xiàn)方法中生成ViewModelStore對(duì)象

//ComponentActivity.java
 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;
    }

2胧后、ViewModelProvider(this)生成AndroidViewModelFactory工廠對(duì)象

//ViewModelProvider.kt
public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))


//AndroidViewModelFactory的伴生對(duì)象
public open class AndroidViewModelFactory(
        private val application: Application
    ) : NewInstanceFactory() {
        @Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                try {
                    modelClass.getConstructor(Application::class.java).newInstance(application)
                } catch (e: NoSuchMethodException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: IllegalAccessException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InstantiationException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InvocationTargetException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                }
            } else super.create(modelClass)
        }

        public companion object {
            internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
                if (owner is HasDefaultViewModelProviderFactory)
                    owner.defaultViewModelProviderFactory else instance

            internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

            private var sInstance: AndroidViewModelFactory? = null

            /**
             * Retrieve a singleton instance of AndroidViewModelFactory.
             *
             * @param application an application to pass in [AndroidViewModel]
             * @return A valid [AndroidViewModelFactory]
             */
            @JvmStatic
            public fun getInstance(application: Application): AndroidViewModelFactory {
                if (sInstance == null) {
                  //**** 生成全局的ViewModelFactory對(duì)象
                    sInstance = AndroidViewModelFactory(application)
                }
                return sInstance!!
            }
        }
    }

3、生成自定義的ViewModel實(shí)例

//ViewModelProvider.kt
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        var viewModel = store[key]
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel)
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        viewModel = if (factory is KeyedFactory) {
            factory.create(key, modelClass)
        } else {
            //此處進(jìn)入上一步創(chuàng)建好的AndroidViewModelFactory的create()方法中去
            factory.create(modelClass)
        }
        store.put(key, viewModel)
        return viewModel
    }


//AndroidViewModelFactory 類
@Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                try {
                  //1.第一步獲取自定義ViewModel的構(gòu)造器抱环,第二部構(gòu)造viewmodel的實(shí)例
                    modelClass.getConstructor(Application::class.java).newInstance(application)
                } catch (e: NoSuchMethodException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: IllegalAccessException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InstantiationException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InvocationTargetException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                }
            } else super.create(modelClass)
        }
    
    

4壳快、ViewModel 觀察界面控制器的生命周期

在界面控制器的構(gòu)造函數(shù)中,就添加了對(duì)生命周期的觀察者江醇,而當(dāng)觀察者收到當(dāng)前的界面控制器的生命周期是Lifecycle.Event.ON_DESTROY時(shí)濒憋,就會(huì)將mViewModelStore對(duì)象map中所有保存的viewModel清理掉,這樣來(lái)達(dá)到釋放資源陶夜。

這里只處理了ON_DESTROY的生命周期狀態(tài)凛驮,那么也就說(shuō)明了在ViewModel對(duì)象實(shí)例創(chuàng)建成功后,不管界面控制器(如Activity)的生命周期(除ON_DESTROY外)如何發(fā)生變化条辟,ViewModel都不會(huì)被清理掉黔夭。

//ComponentActivity.java
public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        //noinspection ConstantConditions
        if (lifecycle == null) {
            throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                    + "constructor. Please make sure you are lazily constructing your Lifecycle "
                    + "in the first call to getLifecycle() rather than relying on field "
                    + "initialization.");
        }
        if (Build.VERSION.SDK_INT >= 19) {
            getLifecycle().addObserver(new LifecycleEventObserver() {
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
                    if (event == Lifecycle.Event.ON_STOP) {
                        Window window = getWindow();
                        final View decor = window != null ? window.peekDecorView() : null;
                        if (decor != null) {
                            decor.cancelPendingInputEvents();
                        }
                    }
                }
            });
        }
        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();
                    }
                }
            }
        });

        if (19 <= SDK_INT && SDK_INT <= 23) {
            getLifecycle().addObserver(new ImmLeaksCleaner(this));
        }
    }

5、存儲(chǔ)ViewModel的數(shù)據(jù)結(jié)構(gòu)

//ViewModelStore.java
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();
    }
}


6羽嫡、Hashmap存放ViewModel的key的生成規(guī)則

從這里看出來(lái)ViewModel對(duì)應(yīng)key的唯一性

//ViewModelProvider.kt

internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
  
        //這里需要注意下這個(gè)canonicalName是個(gè)什么東西
        val canonicalName = modelClass.canonicalName
            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }


//Class.java 
public String getCanonicalName() {
        if (isArray()) {
            String canonicalName = getComponentType().getCanonicalName();
            if (canonicalName != null)
                return canonicalName + "[]";
            else
                return null;
        }
        if (isLocalOrAnonymousClass())
            return null;
        Class<?> enclosingClass = getEnclosingClass();
        if (enclosingClass == null) { // top level class
            return getName();
        } else {
            String enclosingName = enclosingClass.getCanonicalName();
            if (enclosingName == null)
                return null;
            return enclosingName + "." + getSimpleName();
        }
    }

四本姥、總結(jié)

ViewModel工作原理的核心技術(shù)點(diǎn):

觀察者模式、工程模式杭棵、反射婚惫、Hashmap數(shù)據(jù)結(jié)構(gòu)

ViewModel在MVVM架構(gòu)模型中,與DataBinding結(jié)合使用魂爪,會(huì)讓你有起飛的感覺(jué)先舷。后續(xù)會(huì)進(jìn)一步加深使用。本篇僅以學(xué)會(huì)使用滓侍、了解原理為重點(diǎn)蒋川。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市撩笆,隨后出現(xiàn)的幾起案子捺球,更是在濱河造成了極大的恐慌缸浦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氮兵,死亡現(xiàn)場(chǎng)離奇詭異裂逐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胆剧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)絮姆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人秩霍,你說(shuō)我怎么就攤上這事∫涎簦” “怎么了铃绒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)螺捐。 經(jīng)常有香客問(wèn)我颠悬,道長(zhǎng),這世上最難降的妖魔是什么定血? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任赔癌,我火速辦了婚禮,結(jié)果婚禮上澜沟,老公的妹妹穿的比我還像新娘灾票。我一直安慰自己,他們只是感情好茫虽,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布刊苍。 她就那樣靜靜地躺著,像睡著了一般濒析。 火紅的嫁衣襯著肌膚如雪正什。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天号杏,我揣著相機(jī)與錄音婴氮,去河邊找鬼。 笑死盾致,一個(gè)胖子當(dāng)著我的面吹牛主经,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绰上,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼旨怠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蜈块?” 一聲冷哼從身側(cè)響起鉴腻,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤迷扇,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后爽哎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜓席,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年课锌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厨内。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渺贤,死狀恐怖雏胃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情志鞍,我是刑警寧澤瞭亮,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站固棚,受9級(jí)特大地震影響统翩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜此洲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一厂汗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呜师,春花似錦娶桦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至碰酝,卻和暖如春霎匈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背送爸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工铛嘱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袭厂。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓墨吓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纹磺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帖烘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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