ViewModels 簡單入門

簡介

兩年前,我在作 給 Android 入門的課程,教零基礎學生開發 Android App。其中有一部分是教學生構建一個簡單 App 叫作 Court-Counter.前端

Court-Counter 是一個只有幾個按鈕來修改籃球比賽分數的 App。最終的App有一個bug,若是你旋轉手機,當前保存的分數會莫名歸零。java

這是什麼緣由呢?由於旋轉設備會致使 App 中一些 配置發生改變 ,好比鍵盤是否可用,變動設備語言等。這些配置的改變都會致使 Activity 被銷燬重建。

這種表現可讓咱們在作一些特殊處理,好比設備旋轉時變動爲橫向特定佈局。 然而對於新手(有時候老鳥也是)工程師來講,這可能會讓他們頭疼。git

在 Google I/O 2017,Android Framework團隊推出了一套 Architecture Components 的工具集,其中一個處理設備旋轉的問題。github

ViewModel 類旨在以有生命週期的方式保存和管理與UI相關的數據。 這使得數據能夠在屏幕旋轉等配置變化的狀況下不丟失。數據庫

這篇文章是詳細探索ViewModel系列文章中的第一篇。 在這篇文章中,我會:後端

  • 解釋ViewModel知足的基本需求
  • 經過更改 Court-Counter 代碼以使用 ViewModel 解決旋轉問題
  • 仔細審視 ViewModel 和 UI 組件的關聯

潛在的問題

潛在的挑戰是 Android Activity 生命週期 中有不少狀態,而且因爲配置更改,單個Activity可能會屢次循環進入這些不一樣的狀態。架構

Activity 會經歷全部這些狀態,也可能須要把暫時的用戶界面數據存儲在內存中。這裏將把臨時UI數據定義爲UI所需的數據。例子中包括用戶輸入的數據,運行時生成的數據或者是數據庫加載的數據。這些數據能夠是bitmap, RecyclerView 所需的對象列表等等,在這個例子中,是指籃球得分。

之前你可能用過 onRetainNonConfigurationInstance 方法在配置更改期間保存和恢復數據。可是,若是你的數據不須要知道或管理 Activity 所處的生命週期狀態,這樣寫會不會致使代碼過於冗雜?若是 Activity 中有一個像scoreTeamA 這樣的變量,雖然與 Activity 生命週期緊密相連,但又存儲在Activity以外的地方呢?這就是 ViewModel 類的目的ide

在下面的圖表中,能夠看到一個 Activity 的生命週期,該 Activity 經歷了一次旋轉,最後被 finish 掉。 ViewModel 的生命週期顯示在關聯的Activity生命週期旁邊。注意,ViewModels 能夠很簡單的用與Fragments 和 Activities,,這裏稱他們爲 UI 控制器。本示例着重於 Activities。工具

ViewModel從你首次請求建立ViewModel(一般在onCreate的Activity)時就存在,直到Activity完成並銷燬。Activity 的生命週期中,onCreate可能會被調用屢次,好比當應用程序被旋轉時,但 ViewModel 會一直存在,不會被重建。

一個簡單的例子

分三步驟來設置和使用ViewModel:佈局

  1. 經過建立一個擴展 ViewModel 類來從UI控制器中分離出你的數據
  2. 創建你的 ViewModel 和UI控制器之間的通訊
  3. 在 UI 控制器中使用你的 ViewModel

**第一步: 建立 ViewModel 類 **

通常來說,須要爲每一個界面都建立一個ViewModel類。這個ViewModel類將保存與該屏相關的全部數據,提供 getter 和 setter。這樣就將數據與 UI 顯示邏輯分開了,UI邏輯在Activities 或 Fragments中,數據保存在 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控制器和ViewModel

你的UI控制器(Activity或Fragment)須要訪問你的ViewModel。這樣,UI控制器就能夠在UI交互發生時顯示和更新數據,例如按下按鈕以增長 Court-Counter 中的分數。

ViewModels不該該持有 Activities ,Fragments 或者 Context 的引用。

此外,ViewModels也不該包含包含對UI控制器(如Views)引用的元素,由於這將建立對Context的間接引用。

之因此不這樣作是由於,ViewModel 比 UI控制器生命週期長,好比你旋轉一個Activity三次,會獲得三個不一樣的Activity實例,但ViewModel只有一個。

基於這一點,咱們來建立 UI控制器/ ViewMode l的關聯。在UI控制器中將 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...
}
複製代碼

注意: 這裏對 「no contexts in ViewModels」 規則有個例外。有時候你可能會須要一個 Application context(as opposed to an Activity context) 調用系統服務。這種狀況下在 ViewModel 中持有 Application context 是沒問題的,由於 Application context 是存在於 App 整個生命週期的,這點與 Activity context 不一樣, Activity context 只存在與 Activity 的生命週期。事實上,若是你須要 Application context,最好繼承 AndroidViewModel ,這是一個持有 Application 引用的 ViewModel。

第三步:在 UI 控制器中使用 ViewModel

要訪問或更改UI數據,可使用ViewModel中的數據。下面是一個新的 onCreate 方法的示例,以及一個增長 team A 分數的方法:

// 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);
}
複製代碼

tips: ViewModel 也能夠很好地與另外一個架構組件 LiveData 一塊兒工做,在這個系列中我不會深刻探索。使用LiveData 的額外好處是它是可觀察的:它能夠在數據改變時觸發UI更新。能夠在這裏瞭解更多關於LiveData的信息。

進一步審視 ViewModelsProviders.of

第一次調用 ViewModelProviders.of 方法是在 MainActivity 中,建立了一個新的 ViewModel 實例。每次調用 onCreate 方法都會再次調用這個方法。它會返回以前 Court-Counter MainActivity 中建立的 ViewModel。 這就是它持有數據的方式。

只有給 UI controller 提供正確的UI控制器做爲參數才能夠。切記不要在 ViewModel 內存儲 UI 控制器,ViewModel 會在後臺跟蹤 UI 控制器實例和 ViewModel 之間的關聯。

ViewModelProviders._of_(**<THIS ARGUMENT>**).get(ScoreViewModel.**class**);
複製代碼

這可讓你有一個應用程序,打開同一個 Activity or Fragment 的不一樣實例,但具備顯示不一樣的 ViewModel 信息。讓咱們想象一下,若是咱們擴展 Court-Counter 程序,使其能夠支持不一樣的籃球比賽得分。比賽呈如今列表裏,而後點擊列表中的比賽就會開啓一屏與 MainActivity 同樣的畫面,後面我就叫它 GameScoreActivity。

對於你打開的每個不一樣的比賽畫面,在 onCreate 中關聯ViewModel和GameScoreActivity 後,它將建立不一樣的 ViewModel 實例。旋轉其中一個屏幕,則保持與同一個ViewModel的鏈接。

全部這些邏輯都是經過調 ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class) 實現的。 你只須要傳遞正確的UI 控制器實例就好。

最後的思考: ViewModel很是好的把你的UI控制器代碼與UI的數據分離出來。 這就是說,它並非能完成數據持久化和保存App 狀態的工做。 在下一篇文章中,我將探討Activity生命週期與ViewModels之間的微妙交互,以及 ViewModel 與 onSaveInstanceState 進行比較。

結論和進一步的學習

在這篇文章中,我探索了新的ViewModel類的基礎知識。關鍵要點是:

  • ViewModel類旨在一個連續的生命週期中保存和管理與UI相關的數據。這使得數據能夠在屏幕旋轉等配置變化的狀況下得以保存。
  • ViewModels將UI實現與 App 數據分離開來。
  • 通常來講,若是某屏應用中有瞬態數據,則應該爲該屏的數據建立一個單獨的ViewModel。
  • ViewModel的生命週期從關聯的UI控制器首次建立時開始,直到徹底銷燬。
  • 不要將UI控制器或 Context 直接或間接存儲在ViewModel中。這包括在ViewModel中存儲 View。對UI控制器的直接或間接引用違背了從數據中分離UI的目的,並可能致使內存泄漏。
  • ViewModel對象一般會存儲LiveData對象,您能夠在 這裏瞭解更多。
  • ViewModelProviders.of 方法經過做爲參數傳入的 UI控制器與 ViewModel 進行關聯。

想要了解更多 ViewModel 化的好處? 能夠進一步閱讀下面文章:

感謝 Mark Lu, Florina Muntenescu, 以及 Daniel Galpin.


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博知乎專欄

相關文章
相關標籤/搜索