Android架構(gòu)組件(三):ViewModel

前言

上篇我們分析了Livedata的使用及原理做院,相信我們已經(jīng)學(xué)會了使用Livedata來存儲數(shù)據(jù)夺脾,并在觀察者組件中實現(xiàn)回調(diào)方法有鹿,來動態(tài)更新UI數(shù)據(jù)。這里奉上(雙膝已經(jīng)跪爛了...)上兩篇的地址:
Android架構(gòu)組件(一):Lifecycle
Android架構(gòu)組件(二):LiveData
方便大家進行查閱和回顧央勒。

那么不见,接下來我們要學(xué)習(xí)我們的第三個架構(gòu)組件——Viewmodel,我們從字面上理解崔步,它肯定和view脖祈,model有關(guān)聯(lián),它是負責(zé)準備和管理UI組件(activity/fragment)相關(guān)的數(shù)據(jù)類刷晋,也就是說Viewmodel是用來管理UI相關(guān)數(shù)據(jù)的盖高,同時Viewmodel還可以負責(zé)UI間組件的通訊

Viewmodel是什么眼虱?

我們已經(jīng)知道喻奥,Viewmodel有以下兩點作用:

  1. 用來管理數(shù)據(jù)(model)和UI組件(view)的數(shù)據(jù)類
  2. 負責(zé)UI組件之間的通訊
  • 管理數(shù)據(jù)(model)和UI組件(view)的數(shù)據(jù)
    我們先來看一下它的基本使用:
public class MyViewModel extends ViewModel {
    //如果不熟悉Livedata用法可以閱讀上一篇博客
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // 異步調(diào)用獲取用戶列表
        ...
        users.setValue(data);
    }
}

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

用法很簡單,
我們在viewmodel中定義一個livedata的集合,通過網(wǎng)絡(luò)獲取數(shù)據(jù)后捏悬,調(diào)用setValue方法通知觀察者(UI)在活躍狀態(tài)下時更新數(shù)據(jù)撞蚕。
在activity中,我們初始化viewmodel拿到livedata并在他的onchanged()方法里做UI相關(guān)操作过牙。
這里我們發(fā)現(xiàn)viewmodel做了一個中間人的角色甥厦,它管理著model與view之間相互關(guān)聯(lián)的數(shù)據(jù),這樣我們就可以把數(shù)據(jù)相關(guān)(model)操作放到viewmodel中寇钉,把UI操作放到view中刀疙,完全由viewmodel管理,使model與view層完全解耦扫倡。

  • 負責(zé)UI間組件之間的通訊
    一個activity中的多個Fragment互相間通訊時很常見的需求谦秧,我們可以使用activity中的viewmodel來實現(xiàn)fragment之間數(shù)據(jù)的共享。
    下面這個例子也很簡單:
//我們定義viewmodel并設(shè)置set撵溃,get方法
public class CommunicateViewModel extends ViewModel {
    private MutableLiveData<String> mNameLiveData;

    public LiveData<String> getName(){
        if (mNameLiveData == null) {
            mNameLiveData = new MutableLiveData<>();
        }
        return mNameLiveData;
    }

    public void setName(String name){
        if (mNameLiveData != null) {
            mNameLiveData.setValue(name);
        }
    }
}

//我們通過fragment1設(shè)置name的值
public class FragmentOne extends Fragment {
    private CommunicateViewModel mCommunicateViewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
    }

    @OnClick(R.id.btn_set_name)
    void onViewClicked(View v){
        switch (v.getId()){
            case R.id.btn_set_name:
                mCommunicateViewModel.setName("Jane");
                break;
        }
    }
}

//在fragment2中我們通過同一個viewmodel拿到livedata并更新UI
public class FragmentTwo extends Fragment {
    private CommunicateViewModel mCommunicateViewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
        mCommunicateViewModel.getName().observe(this, name -> mTvName.setText(name));
    }
}

上述代碼我們知道了疚鲤,兩個fragment的是通過同一個viewmodel進行組件之間的通訊,這里值得注意的是兩個fragment中初始化viewmodel時傳入的都是getActivity() 這也就意味著他們傳入的是同一個對象缘挑,如果不同集歇,那么得到的將是兩個viewmodel對象,也不會收到通知進行更新了语淘。

這種組件間通訊的好處在于

  • activity不需要做任何事情
  • fragment不需要知道彼此诲宇,而是通過viewmodel進行聯(lián)系

Viewmodel分析

我們先來看下它的聲明周期圖:


viewmodel生命周期

從上圖我們分析得出际歼,左側(cè)表示Activity的生命周期狀態(tài),右側(cè)綠色部分表示ViewModel的生命周期范圍焕窝。當(dāng)屏幕旋轉(zhuǎn)的時候,Activity會被recreate维贺,Activity會經(jīng)過幾個生命周期方法它掂,但是這個時候ViewModel還是之前的對象,并沒有被重新創(chuàng)建溯泣,只有當(dāng)Activity的finish()方法被調(diào)用時,ViewModel.onCleared()方法會被調(diào)用,對象才會被銷毀扛禽。這張圖很好的描述了當(dāng)Activity被recreate時坤学,ViewModel的生命周期。

另外肢簿,有個注意的地方:在ViewModel中不要持有Activity的引用靶剑。為什么要注意這一點呢?從上面的圖我們看到池充,當(dāng)Activity被recreate時桩引,ViewModel對象并沒有被銷毀,如果Model持有Activity的引用時就可能會導(dǎo)致內(nèi)存泄漏收夸。那如果你要使用到Context對象怎么辦呢坑匠,ViewModel的子類AndroidViewModel為我們很好的解決了這一問題,我們稍后會分析卧惜。

我們再來看一下viewmodel的類圖:

viewmodel類圖

根據(jù)這張類圖厘灼,我們來分析一下:

  • ViewModelProviders是ViewModel工具類,該類提供了通過Fragment和Activity得到ViewModel的方法咽瓷,而具體實現(xiàn)又是由ViewModelProvider實現(xiàn)的设凹。
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    //param1是ViewModelStore,param2是工廠類
    return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
//他在of()方法里初始化了ViewModelProvider里的工廠類AndroidViewModelFactory茅姜,并renturn了ViewModelProvider對象
//內(nèi)部還有一些check方法用于檢查Fragment是否Attached to Activity围来,Activity的Application對象是否為空等
  • ViewModelStores是ViewModelStore的工廠方法類,它會關(guān)聯(lián)Fragment匈睁,activity
    上個代碼片段我們看見它在of()方法里renturn時new了一個provider對象并通過ViewModelStores.of()得到stroe對象

//ViewModelStores.of(activity)方法返回了ViewModelStore對象
return new ViewModelProvider(ViewModelStores.of(activity), factory);

public static ViewModelStore of(@NonNull FragmentActivity activity) {
    if (activity instanceof ViewModelStoreOwner) {
        return ((ViewModelStoreOwner) activity).getViewModelStore();
    }
    //我們看見這里有一個holderFragmentFor對象(HolderFragment)
    return holderFragmentFor(activity).getViewModelStore();
}

public HolderFragment() {
    //將這個方法設(shè)置為true就可以使當(dāng)前Fragment在Activity重建時存活下來,如果不設(shè)置或者設(shè)置為false,當(dāng)前Fragment會在Activity重建時同樣發(fā)生重建,以至于被新建的對象所替代监透。
    setRetainInstance(true);
    //這樣就解決了旋轉(zhuǎn)屏幕時因為重建導(dǎo)致數(shù)據(jù)丟失的問題
}
//在HoldFragment中初始化了ViewModelStore用于在銷毀時clear,釋放掉viewmodel
private ViewModelStore mViewModelStore = new ViewModelStore();
@Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
  • ViewModelStore是存儲ViewModel的類航唆,具體實現(xiàn)是通過HashMap來保存ViewModle對象胀蛮。

//viewmodelStroe用戶存儲Viewmodel,并提供set糯钙,get方法和clear方法
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);
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            //這里調(diào)用了viewmodel的onCleared()方法
            vm.onCleared();
        }
        mMap.clear();
    }
}
  • ViewModelProvider是實現(xiàn)ViewModel創(chuàng)建粪狼、獲取的工具類退腥。在ViewModelProvider中定義了一個創(chuàng)建ViewModel的接口類——Factory。ViewModelProvider中有個ViewModelStore對象再榄,用于存儲ViewModel對象狡刘。

//構(gòu)造方法中我們傳入了stroe和工廠類
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore = store;
}
//我們使用的.get(modele.class)方法最終會調(diào)用這個get方法
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //從map中獲取model對象
    ViewModel viewModel = mViewModelStore.get(key);
    //判斷是否是同一個對象?如果是return此viewmode
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    } else {
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //如果不是則通過工廠類創(chuàng)建困鸥,然后緩存進stroe嗅蔬,并return
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

好了,至此我們通過閱讀源碼疾就,已經(jīng)對viewmodel的工作原理有了一定的了解澜术,那么們就來總結(jié)一下它如何通過一系列操作,來做到對view和model進行管理的猬腰。

//1. 首先我們會在繼承viewmodel的類中鸟废,做一些數(shù)據(jù)操作(初始化livedata),并提供set,get方法返回livedata對象姑荷。(代碼省略...查看開頭基本用法的代碼塊)
//2. 我們在view組件(activity/fragment)中拿到viewmodel對象
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
//3. of()方法返回viewmodelprodiver對象
 public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
    //4. 在of方法里初始化ViewModelProvider中AndroidViewModelFactory對象
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    //5. ViewModelProvider中傳入ViewModelstore和factory對象
    return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
//6. 工廠類 ViewModelStores.of(activity)方法返回ViewModelStore對象
public static ViewModelStore of(@NonNull FragmentActivity activity) {
    if (activity instanceof ViewModelStoreOwner) {
        return ((ViewModelStoreOwner) activity).getViewModelStore();
    }
    //7.holderFragmentFor對象(HolderFragment)解決了屏幕旋轉(zhuǎn)時數(shù)據(jù)保存盒延。setRetainInstance(true);在里面初始化了ViewModelStore對象
    return holderFragmentFor(activity).getViewModelStore();
}
//8. 這時我們拿到了ViewModelProviders.of(this)返回的provider對象,然后調(diào)用get方法.get(MyViewModel.class);最終走到此get()方法
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //從map中獲取model對象
    ViewModel viewModel = mViewModelStore.get(key);
    //判斷是否是同一個對象鼠冕?如果是return此viewmode
    if (modelClass.isInstance(viewModel)) {
        return (T) viewModel;
    } else {
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //如果不是則通過工廠類創(chuàng)建兰英,然后緩存進stroe,并return
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}
//9. 返回了viewmodel對象供鸠,通過viewmodel的get方法拿到livedata對象畦贸,并在ui組件處于活躍狀態(tài)時更新UI
model.getUsers().observe(this, users -> {
            // 更新 UI
        });

參考&感謝

Android架構(gòu)組件——ViewModel

玩Android

總結(jié)

Viewmodel的職責(zé)是為UI組件管理數(shù)據(jù)。規(guī)范化viewmodel的使用方式楞捂,不要在viewmodel層中持有UI層的引用薄坏,避免因viewmodel超長的生命周期,導(dǎo)致內(nèi)存泄漏寨闹。實現(xiàn)UI組件和數(shù)據(jù)間的管理和解耦胶坠,才是這個框架帶給我們的理解。
通過我們對源碼的分析繁堡,它的功能并不復(fù)雜沈善,但設(shè)計的十分巧妙,背后摻雜的思想和理念才是值得去反復(fù)揣度的椭蹄。它可以更好的實現(xiàn)把業(yè)務(wù)代碼下沉到viewmodel中實現(xiàn)闻牡,既保證了UI組件中代碼的清爽,又可以實現(xiàn)對數(shù)據(jù)的管理绳矩。

Viewmodel可以用于activity中不同fragment之間的通信罩润,也可以用作Fragment之間一種解耦方式。

接下來我們會講到Android架構(gòu)的另一個組件Room翼馆,來看下這個數(shù)據(jù)庫能帶給我們哪些驚艷割以?
當(dāng)學(xué)習(xí)完所有的組件后金度,我們就開始嘗試著去搭一款適合自己的MVVM框架,用于加深我們對Android架構(gòu)組件的學(xué)習(xí)严沥,從而做到學(xué)以致用猜极。

Android架構(gòu)組件系列文章

我的博客(Power)
Android架構(gòu)組件(一):Lifecycle
Android架構(gòu)組件(二):LiveData
Android架構(gòu)組件(三):Viewmodel
Android架構(gòu)組件(四):Room

感謝您的閱讀和支持!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末消玄,一起剝皮案震驚了整個濱河市跟伏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莱找,老刑警劉巖酬姆,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗜桌,死亡現(xiàn)場離奇詭異奥溺,居然都是意外死亡,警方通過查閱死者的電腦和手機骨宠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門浮定,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人层亿,你說我怎么就攤上這事桦卒。” “怎么了匿又?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵方灾,是天一觀的道長。 經(jīng)常有香客問我碌更,道長裕偿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任痛单,我火速辦了婚禮嘿棘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旭绒。我一直安慰自己鸟妙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布挥吵。 她就那樣靜靜地躺著重父,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忽匈。 梳的紋絲不亂的頭發(fā)上坪郭,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音脉幢,去河邊找鬼歪沃。 笑死嗦锐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沪曙。 我是一名探鬼主播奕污,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼液走!你這毒婦竟也來了碳默?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤缘眶,失蹤者是張志新(化名)和其女友劉穎嘱根,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巷懈,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡该抒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了顶燕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凑保。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涌攻,靈堂內(nèi)的尸體忽然破棺而出欧引,到底是詐尸還是另有隱情,我是刑警寧澤恳谎,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布芝此,位于F島的核電站,受9級特大地震影響因痛,放射性物質(zhì)發(fā)生泄漏婚苹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一婚肆、第九天 我趴在偏房一處隱蔽的房頂上張望租副。 院中可真熱鬧,春花似錦较性、人聲如沸用僧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽责循。三九已至,卻和暖如春攀操,著一層夾襖步出監(jiān)牢的瞬間院仿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歹垫,地道東北人剥汤。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像排惨,于是被迫代替她去往敵國和親吭敢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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