大約兩年前,我在傳授Android for Beginners課程,這是一門讓零編程基礎的學生學習如何編寫第一個Android應用程序的課程。做爲課程的一部分,學生們將開發一個很是簡單的名爲Court-Counter的應用程序。html
Court-Counter是一個很是簡單的應用程序,只有一個界面,裏面提供了一些按鈕用於修改籃球比賽的比分。學生們最終完成的應用程序有都存在一個bug: 若是旋轉手機的屏幕,應用程序界面上的當前比分將莫名其妙地丟失。java
這是怎麼回事?旋轉設備的屏幕是應用程序在其生命週期中可能經歷的一些configuration changes中的一種,其它還包括等改設備的語言等。 全部這些configuration changes都會致使Activity被銷燬並從新建立。 Android系統的這種機制可讓咱們作一些有趣的事情,好比在設備旋轉的時候使用橫向佈局,但它卻可能會讓Android開發新手頭疼不已。android
在2017年Google I / O大會上,Android Framework團隊推出了一套新的Architecture Components,其中的一個組件ViewModel就是用來處理這個屏幕的旋轉問題。git
ViewModel 類旨在以一種可以感知生命週期的方式來保存和管理與UI相關的數據,這使得數據可以在configuration changes(如屏幕旋轉)的時候不會丟失。github
這篇文章是探索ViewModel細節的系列文章中的第一篇。 在這篇文章中,我會:數據庫
問題的根本緣由在於Activity的生命週期有不少不一樣的狀態,而且因爲configuration changes,一個Activity可能會屢次經歷這些不一樣的狀態。編程
當一個Activity正在經歷全部的這些狀態時,您可能還須要在內存中保存一些UI的臨時數據。我將UI的臨時數據定義爲UI所需的數據。它包括用戶輸入的數據,應用在運行時生成的數據或者是從數據庫加載的數據。這些數據多是位圖圖像,RecyclerView所需的對象列表,或者是本文中提到的籃球得分。bash
ViewModel出現以前,在configuration changes的時候您可能會使用onRetainNonConfigurationInstance方法來保存此數據,並使用getLastNonConfigurationInstance方法來取出這些數據。可是若是你的數據不須要知道Activity正處於處生命週期的哪一種狀態,它會不會無限膨脹?若是這些數據不是像Activity的變量scoreTeamA那樣,與Activity的生命週期緊密相,而是存儲在Activity以外的其餘位置,該怎麼辦? 這正是ViewModel類存在的意義。架構
在下面的圖表中,您能夠看到一個Activity的生命週期,該Activity經歷了一次屏幕旋轉,而後最終被finish。ViewModel的生命週期顯示在相對應的Activity生命週期的旁邊。 請注意,ViewModels能夠很方便的用在Fragment和Activity裏,我將稱其爲UI controllers。本文重點介紹的是在Activity裏如何使用ViewMode。ide
從你第一次請求ViewModel(一般在onCreate Activity中)開始到Activity最終被銷燬,ViewModel會一直存在。 onCreate可能會在Activity的生命週期中屢次調用,例如設備的屏幕發生旋轉,但ViewModel仍是同一個ViewModel。ViewModel的使用能夠分爲一下三個步驟:
通常來講,您須要爲您應用中的每一個界面建立一個ViewModel類。 這個ViewModel類將保存與界面相關的全部數據,併爲存儲的數據提供getter和setter方法。這樣就將用戶界面(在Activity和Fragment中實現)中須要顯示的數據從UI controllers中分離出來,如今該數據位於ViewModel中。 因此,讓咱們爲Court-Counter中的一個界面建立一個ViewModel類
public class ScoreViewModel extends ViewModel {
// Tracks the score for Team A
public int scoreTeamA = 0;
// Tracks the score for Team B
public int scoreTeamB = 0;
}
複製代碼
爲了簡單起見,我選擇將數據做爲公開的成員變量存儲在ScoreViewModel.java中,但建立getter和setter方法以更好地封裝數據是個不錯的主意。
你的UI controllers(這裏是指Activity或Fragment)須要知道你的ViewModel,由於用戶在與UI發生交互的時候須要顯示數據和更新數據,例如按下按鈕以增長Court-Counter計數器中的團隊得分。 ViewModels不該該包含Activity、Fragment或Context的引用。此外,ViewModels還不該包含對UI controllers中的變量(如Views)的引用,由於這將建立對Context的間接引用。
不要在ViewModels裏存儲這些對象的緣由是ViewModels是獨立於你的UI controllers實例以外的- 若是你三次旋轉一個Activity的屏幕方向,那麼系統會建立三個不一樣的Activity實例,但你只有一個ViewModel。
考慮到這一點,咱們須要實現這個UI controller 與 ViewModel之間關聯。 您須要在UI controller中爲您的ViewModel建立一個成員變量。 而後在onCreate中調用:
ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
複製代碼
在Court-Counter裏是這樣寫的:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
// Other setup code below...
}
複製代碼
注意: 「ViewModels中不該包含Context」這個原則有一個例外狀況。 有時您可能須要Application context (而不是Activity context )以獲取諸如系統服務之類的東西。將Application context存儲在ViewModel中是能夠的,由於Application context是與應用程序的生命週期相關聯的。這與Activity context不一樣,後者與Activity的生命週期相關聯。事實上,若是你須要一個Application context,你應該繼承AndroidViewModel類,它是一個包含Application context的ViewModel。
如今你能夠在ViewModel中獲取或更改UI中數據了。 下面是一個新的onCreate方法的示例:
// The finished onCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
displayForTeamA(mViewModel.scoreTeamA);
displayForTeamB(mViewModel.scoreTeamB);
}
// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
displayForTeamA(mViewModel.scoreTeamA);
}
複製代碼
提示:ViewModel也能夠與架構中的另外一個組件LiveData一塊兒工做,我不會在本系列中深刻探討。使用LiveData的好處在於它是可觀察的:它能夠在數據更改時觸發UI的更新。您能夠在這裏瞭解更多關於LiveData的信息。
當MainActivity第一次調用ViewModelProviders.of方法的時候,它將建立一個新的ViewModel實例。 以後MainActivity裏的onCreate()方法每一次被調用的時候,ViewModelProviders.of也一樣會被調用,可是它將返回與MainActivity相關聯的預先存在的ViewModel,這就是ViewModel能夠保存數據的緣由。
前提條件是你必須傳入正確的UI controller來做爲ViewModelProviders.of的第一個參數。 雖然你不該該在ViewModel中存儲UI controller,但ViewModel類會使用您傳入的UI controller做爲第一個參數來跟蹤ViewModel和UI controller之間的關聯關係。
ViewModelProviders.of(<THIS ARGUMENT>).get(ScoreViewModel.class);
複製代碼
ViewModelProviders.of使得你的應用能夠打開同一Activity或Fragment的不一樣實例,但ViewModel中卻保存着不一樣的信息。
咱們能夠把Court-Counter擴展一下,使它能記錄和顯示多場籃球比賽的分數。比賽以列表形式呈現,而後點擊列表中的某一場比賽會打開一個看起來像咱們當前的MainActivity的界面,這裏我稱之爲GameScoreActivity。
對於您打開的每場比賽所對應的GameScoreActivity,若是在GameScoreActivity的onCreate方法將其與ViewModel關聯起來,它將建立一個不一樣的ViewModel實例。若是旋轉其中一個界面的屏幕,則保持與同一ViewModel的鏈接。
全部這些邏輯都是經過調用ViewModelProviders.of(Your UI controller)get(Your ViewModel.class)方法來完成的。 因此只要你傳入一個UI控制器的正確實例,它就能夠正常工做。
最後我想說:ViewModels真的很好,能夠將填充數據到視圖的邏輯從UI controller分離出來。這意味着,它並非一種數據持久化和保存應用程序狀態的解決方案。在下一篇文章中,我將研究Activity生命週期與ViewModels之間的交互,並將ViewModel與onSaveInstanceState進行比較。
在這篇文章中,我介紹了一些關於ViewModel類的基礎知識。關鍵要點是: