ViewModel的使用

前言

Android開發(fā)過程中舞丛,為了避免Activity過于臃腫,我們采用了LifeCycle來解耦生命周期相關(guān)的業(yè)務(wù)邏輯果漾。但是在開發(fā)中我們?nèi)匀恢铝τ诮鉀Q另一個問題球切,那就是數(shù)據(jù)和ui分離。今天就來講講用來解決Activity臃腫及其他相關(guān)問題的ViewModel的使用
今天涉及知識由有:

  1. 為什么要用ViewModel
  2. ViewModel初始化方式
  3. 封裝BaseViewModel的使用
  4. ViewModel使用注意事項
  5. BaseViewModle源碼

一. 為什么要用 ViewModel

這里主要講講使用ViewModel的好處:

  • 達(dá)到數(shù)據(jù)绒障,圖形分離(解耦)
  • ViewModel實現(xiàn)數(shù)據(jù)持有不丟失
  • 基于第二點吨凑,還能實現(xiàn)ActivityFragment間數(shù)據(jù)共享

二. ViewModel 初始化方式

網(wǎng)上多出現(xiàn)ViewModel的使用需要引入jar,但我在實際使用過程中,發(fā)現(xiàn)不需要添加額外依賴户辱,顯示ViewModel是在androidx.lifecycle包下鸵钝。
ViewModelMainActivity中使用為例,首先我們要建一個MainViewModel繼承自ViewModel,然后在MainActivity中實例化它如下:

mainViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(MainViewModel::class.java)

這里采用的是工廠模式初始化庐镐。this是當(dāng)前Activity實例恩商,從初始化方法的源碼中可以看出

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

參數(shù)應(yīng)該為ViewModelStoreOwner owner,而追蹤MainActivity源碼可發(fā)現(xiàn):

MainActivity -> AppCompatActivity -> FragmentActivity -> ComponentActivity

然后看ComponentActivity:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller {

可以發(fā)現(xiàn)ComponentActivityViewModelStoreOwner實現(xiàn)類,即MainActivityViewModelStoreOwner實例必逆,所以初始化時可以用this代替ViewModelStoreOwner owner參數(shù)怠堪。
當(dāng)然,我們還可以用以下方式在MainActivity中初始化ViewModel對象:

mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)

當(dāng)然名眉,我們還可以通過自定義工廠模式來實現(xiàn)ViewModel的初始化研叫,這個接下來講。

三. 封裝 BaseViewModel 的使用

既然有以上兩種初始化ViewModel的方式璧针,我們?yōu)樯哆€要自定義工廠模式來實現(xiàn)ViewModel的初始化呢嚷炉?原因是我們在開發(fā)的時候不免會涉及到ViewModel傳值的問題,很顯然探橱,以上兩種方式均不能在初始化時將MainActivity的數(shù)據(jù)傳輸?shù)?code>ViewModel中去申屹。于是我封裝了一個BaseViewModel用以實現(xiàn)MainActivityViewModel傳值的目的。下面以簡單例子來講解ViewModel的好處隧膏。先繼承BaseViewModel實現(xiàn)MainViewModel用以緩存界面MainActivity中數(shù)據(jù):

class MainViewModel : BaseViewModel {

    constructor()

    constructor(savedInstanceState: Bundle?, any:Any?):super(savedInstanceState,any){

    }

    var mNumber:Int=5
}

由于我對BaseViewModel做了處理哗讥,所以我們在繼承BaseViewModel實現(xiàn)MainViewModel時,必須寫一階構(gòu)造函數(shù)constructor()胞枕,否則會在使用過程中報錯杆煞。
接著看MainActivity代碼:

@RequiresApi(Build.VERSION_CODES.N)
class MainActivity : AppCompatActivity(), View.OnClickListener {

    private lateinit var mBingding: ActivityMainBinding
    private lateinit var mainViewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBingding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBingding.root)

        initData(savedInstanceState)
        setListener()
    }

    private var initData:(Bundle?)->Unit = {savedInstanceState->
        mBingding.tvName.text = "我是誰"
        //初始化mainViewModel
        mainViewModel=BaseViewModel.getViewModel(this,object: BaseViewModel.BaseViewModelFactory(savedInstanceState,"小學(xué)") {
            override fun getChildViewModel(savedInstanceState: Bundle?, any: Any?): BaseViewModel {
                return MainViewModel(savedInstanceState,any)
            }
       },MainViewModel::class.java) as MainViewModel

        mBingding.tvName.text = mainViewModel.mNumber.toString()
    }

    private var setListener = {
        mBingding.mBtnTest.setOnClickListener(this)
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.mBtnTest -> {
                LogUtil.i("====我點擊了=====")

                plusNumber()

            }
            else -> {

            }
        }
    }

    private var plusNumber = {
        mainViewModel.mNumber=mainViewModel.mNumber+1
        mBingding.tvName.text=mainViewModel.mNumber.toString()
    }

    override fun onDestroy() {
        super.onDestroy()
    }

}

運行效果圖如下:

1.gif

這里我們可以發(fā)現(xiàn),通過ViewModel來保存數(shù)據(jù)以后,我們在屏幕旋轉(zhuǎn)時决乎,之前的數(shù)據(jù)得以保留队询,而不是回到初始值。這就是ViewModel數(shù)據(jù)持有的獨特魅力构诚。

四. ViewModel 使用注意事項

4.1 ViewModel 生命周期

ViewModel生命周期在網(wǎng)上有一幅圖

image.png

Activity在系統(tǒng)配置變化導(dǎo)致的重建不會銷毀ViewModel蚌斩,ViewModel對象會保留并關(guān)聯(lián)到新的Activity,只有在Activity正常銷毀(系統(tǒng)不會重建Activity)時,ViewModel才會被銷毀。通過代碼:

mainViewModel= ViewModelProvider(this).get(MainViewModel::class.java)

追蹤源碼:

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

繼續(xù)看this方法:

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

可發(fā)現(xiàn)mViewModelStore被賦值范嘱,然后接著看.get(MainViewModel::class.java)這部分:

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

接著追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)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            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中若有viewModel則直接取送膳,沒有的話,則由mFactory創(chuàng)建丑蛤。mFactory創(chuàng)建方法如下:

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

至此叠聋,我們可以發(fā)現(xiàn),一個Activity中只有一個ViewModelStore受裹,ViewModelStore可存儲多個ViewModel對象碌补,ViewModel初始化流程是如果ViewModelStoreViewModelnew一個ViewModel并放到ViewModelStore中存儲,如果ViewModelStore有則直接取出來用名斟。

4.2 ViewModel 不能持有的對象

鑒于ViewModel生命周期跨越的長度比較大(包含一個Activity的整個生命周期),我們可以利用ViewModel來做ActivityFragment間的傳值處理魄眉。但是也由于生命周期過長的問題砰盐,我們不能在ViewModel中出現(xiàn)以下兩種對象:

  • ui控件實例
  • Activity/Fragment實例

ViewModel中出現(xiàn)UI控件,會導(dǎo)致程序崩潰坑律,出現(xiàn)Activity/Fragment實例岩梳,輕則內(nèi)存泄漏,重則崩潰

4.3 ViewModel 中使用 Application

ViewModel中不能出現(xiàn)Activity/Fragment實例晃择,那我們在ViewModel中要使用Context對象怎么辦冀值?這時我們不能用Activity/Fragmentcontext,而要使用Applicationcontext,我們可以讓自己的ViewModel繼承AndroidViewModel類,類似下面這樣:

class NeViewModel(application: Application) : AndroidViewModel(application) {

}

由于AndroidViewModelViewModel的一個子類宫屠,所以也可以用

neViewModel= ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory(application)).get(NeViewModel::class.java)

neViewModel= ViewModelProvider(this).get(NeViewModel::class.java)

的方式初始化列疗。

4.4 ViewModel 與 savedInstanceState

我們知道savedInstanceState能在activity異常時提供數(shù)據(jù)的瞬時保存與恢復(fù),現(xiàn)在ViewModel也能達(dá)到數(shù)據(jù)瞬時存儲和恢復(fù)的目的浪蹂,那是不是意味著我們有了ViewModel后就不需要savedInstanceState了呢抵栈?
答案是否定的。為了程序的健壯性坤次,我們得同時使用savedInstanceStateViewModel古劲。ViewModel存儲數(shù)據(jù)較多,數(shù)據(jù)持有環(huán)境一般為Activity配置發(fā)生變化導(dǎo)致的重啟缰猴,如果Activity內(nèi)存溢出導(dǎo)致崩潰重啟产艾,ViewModel數(shù)據(jù)也會丟失,這時就需要savedInstanceState協(xié)助。但是savedInstanceState是一個Bundle闷堡,只能存儲少數(shù)數(shù)據(jù)隘膘。所以需要ViewModelsavedInstanceState結(jié)合使用。

五. BaseViewModle 源碼

下面給出BaseViewModle源碼:

還有 22% 的精彩內(nèi)容
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
支付 ¥3.00 繼續(xù)閱讀
  • 序言:七十年代末缚窿,一起剝皮案震驚了整個濱河市棘幸,隨后出現(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)容