viewmodel:一個簡單的例子

介紹

ViewModel 類旨在以注重生命週期的方式存儲和管理界面相關的數據。ViewModel 類讓數據可在發生屏幕旋轉等配置更改後繼續存在(出自官方文檔)。舉個例子,如果系統銷燬或重新創建界面控制器,則存儲在其中的任何臨時性界面相關數據都會丟失。例如,應用的某個 Activity 中可能包含用戶列表。因配置更改而重新創建 Activity 後,新 Activity 必須重新提取用戶列表。對於簡單的數據,Activity 可以使用 onSaveInstanceState() 方法從 onCreate() 中的Bundle恢復其數據,但此方法僅適合可以序列化再反序列化的少量數據,而不適合數量可能較大的數據,如用戶列表或位圖。viewmodel可以完成onSaveInstanceState()的功能,且更強大,他提供了更多的可能性與擴展性

一個簡單的例子

使用ViewModel大概分爲三個步驟

  • 通過創建繼承自ViewModel的類,將數據與UI控制器分離(Activity,Fragment)
  • 在UI控制器中關聯ViewModel
  • 在UI控制器中使用ViewModel
步驟1 通過創建繼承自ViewModel的類,將數據與UI控制器分離(Activity,Fragment)

首先你需要添加androidx軟件包庫中的ViewModel依賴

def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

或通過官方文檔獲取最新版本。
我們通常需要爲每個ui控制器都創建一個viewmodel,這個viewmodel的作用就是保存和屏幕關聯的所有數據,並提供對應的getter/setter方法,下面是一個繼承自viewmodel的類

class MainViewModel : ViewModel() {
	var counterB: Int = 0
}

這裏演示最簡單的使用場景,就不提供get/set了

步驟2 在UI控制器中關聯ViewModel

在UI控制器中建立兩者的關聯

 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ViewModelProvider(this).get(MainViewModel::class.java)
}	
步驟3 在UI控制器中使用ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

    var counterA = 0

    val text1 = findViewById<TextView>(R.id.text1)
    val text2 = findViewById<TextView>(R.id.text2)
    val button = findViewById<Button>(R.id.button)
    //在銷燬創建後賦值一遍
    text2.text = viewModel.counterB.toString()
    text1.text = counterA.toString()
    button.setOnClickListener {
        counterA++
        viewModel.counterB++

        text1.text = counterA.toString()
        text2.text = viewModel.counterB.toString()
    }
}

這裏只提供了一個最簡單的場景,可以看到即使程序因爲旋轉而銷燬重新創建但是在viewModel中的數據並不受影響

在這裏插入圖片描述

viewModel的生命週期

下圖說明了Activity經歷屏幕旋轉後所處的各種生命週期狀態,這些狀態同樣適用於Fragment,通常在onCreate()方法時請求ViewModel,ViewModel存在的時間範圍是從首次請求ViewModel直到Activity完成並銷燬,viewmodel不要持有ui控制器的引用,或持有Context引用的任何類,如果需要在viewmodel中使用Context,如獲取系統服務,可以使用Application Context,理想情況下,viewModel應該對ui控制器一無所知,這提高了可測試性、泄露安全性和模塊化性,一般推薦通過觀察者模式和UI控制器通信,如LiveData,這也是目前較爲通用的一種方式。

在這裏插入圖片描述

viewModel在同一個UI控制器的onCreate()方法中因異常銷燬而多次被調用時,所創建的ViewModel是同一個,這就是保存數據的原因

MainViewModel@fcb7e9a   
MainViewModel@fcb7e9a   
MainViewModel@fcb7e9a   
MainViewModel@fcb7e9a   
MainViewModel@fcb7e9a   

在這裏插入圖片描述

viewModel只保存瞬態數據而非持久化存儲

一旦關聯的ui控制器完成或進程銷燬,則ViewModel和所有包含的數據都會被回收,

viewModel是否可以替代onSaveInstanceState?

不是替代,但他們相關。
之前在網上看到有人說有onSaveInstanceState我爲啥要用viewmodel?,只有深入瞭解二者的差異你才能更好的選擇。
onSaveInstanceState在默認的情況下就會保存視圖上的一些瞬時信息,如EditText的輸入內容,以便在onRestoreInstanceState或onCreate還原,很關鍵的一個信息是onSaveInstanceState的調用時機是在活動即將銷燬前,由於其運行在主線程,大量數據帶來的序列化與反序列化所造成的消耗可能會造成丟幀或視覺卡頓,以及進入後臺時調用,這種情況下其實可能並不需要保存數據
同時ViewModel可以替代使用Fragment setRetainInstance(true)來保留大量數據例如圖像,或複雜對象。使用ViewModel你可以在ViewModel中請求網絡數據並進行臨時存儲,當配置更改時不至於重新請求網絡數據。
總結來說:

  • ViewModel的保存在配置更改之上,因此在配置更改時無需重新查詢外部數據(網絡或數據庫)
  • 不在UI管理器中管理數據,這是良好的設計
  • onSaveInstanceState()用於保存少量瞬態數據,ViewModel可以保存大量數據
  • ViewModel可以委派外部數據加載,並且在加載完成後臨時保存
  • onSaveInstanceState()在配置更改期間以及活動進入後臺時被調用,當系統內存不足時進程被殺死也會被調用。
  • ViewModel 在系統發起的進程終止過程中會被銷燬。因此,您應將 ViewModel 對象與 onSaveInstanceState()(或其他一些磁盤持久性功能)結合使用,並將標識符存儲在 savedInstanceState 中,以幫助視圖模型在系統終止後重新加載數據。

我應該怎麼選擇?

  • ViewModel:在內存中存儲顯示關聯界面控制器所需的所有數據。
  • onSaveInstanceState():存儲當系統停止後又重新創建界面控制器時輕鬆重新加載 Activity 狀態所需的少量數據。這裏指的是將複雜的對象保留在本地存儲空間中,並將這些對象的唯一 ID 存儲在 onSaveInstanceState() 中,而不是存儲複雜的對象。

Google推薦的使用方法是結合使用,所以如果有人問你二者取其一你怎麼回答,現在清楚了嗎。

ViewModel+LiveData-與生命週期聯動的更新

一個簡單的例子
class MainViewModel() : ViewModel() {
//    var counterB: Int = 0
val data = mutableListOf("關羽", "張飛", "黃忠", "馬超", "趙雲")

private val lists: MutableLiveData<List<String>>? by lazy {
    MutableLiveData<List<String>>().apply {
        value = data
    }
}

fun getList(): MutableLiveData<List<String>> {
    return lists!!
}
}

沒有從外部獲取數據,模擬了一個簡單的賦值過程。

  liveData.observe(this, Observer {
        //update UI
  })
介紹:

LiveData 是一種可觀察的數據存儲器類,和UI控制器的生命週期關聯,可以確保僅更新處於活躍狀態的觀察者。
只有當觀察者的生命週期處於 STARTED 或 RESUMED 狀態LiveData纔會調用onChanged,全部狀態如下:

 /**
     * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
     * any more events. For instance, for an {@link android.app.Activity}, this state is reached
     * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
     */
    DESTROYED,

    /**
     * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
     * the state when it is constructed but has not received
     * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
     */
    INITIALIZED,

    /**
     * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
     * is reached in two cases:
     * <ul>
     *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
     *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
     * </ul>
     */
    CREATED,

    /**
     * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
     * is reached in two cases:
     * <ul>
     *     <li>after {@link android.app.Activity#onStart() onStart} call;
     *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
     * </ul>
     */
    STARTED,

    /**
     * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
     * is reached after {@link android.app.Activity#onResume() onResume} is called.
     */
    RESUMED;

    /**
     * Compares if this State is greater or equal to the given {@code state}.
     *
     * @param state State to compare with
     * @return true if this State is greater or equal to the given {@code state}
     */
    public boolean isAtLeast(@NonNull State state) {
        return compareTo(state) >= 0;
    }

在源碼中驗證:

	 return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);

通過傳入STARTED判斷自然順序只有滿足STARTED/RESUMED,纔會通知觀察者。雖然沒有立即通知,但是LiveData記錄下了數據,當狀態變化爲STARTED/RESUMED時依然會通知到觀察者。

LiveData僅在數據發生變化時纔會通知觀察者,且只發送給活躍狀態的觀察者。

postValue,如果你需要在子線程通知觀察者你應該使用他,如下

	class MainViewModel() : ViewModel() {
//    var counterB: Int = 0
val data = mutableListOf("關羽", "張飛", "黃忠", "馬超", "趙雲")

private val lists: MutableLiveData<List<String>> by lazy {
    MutableLiveData<List<String>>().apply {
        Timer().schedule(timerTask {
            lists.postValue(data)
        },2000)
    }
}

fun getList(): MutableLiveData<List<String>> {
    return lists
}
}

使用LiveData有如下好處:

  • 不會發生內存泄露
    • 觀察者會綁定到 Lifecycle 對象,並在其關聯的生命週期遭到銷燬後進行自我清理。
  • 不會因 Activity 停止而導致崩潰
    • 如果觀察者的生命週期處於非活躍狀態(如返回棧中的 Activity),則它不會接收任何 LiveData 事件。
  • 不再需要手動處理生命週期
    • 界面組件只是觀察相關數據,不會停止或恢復觀察。LiveData 將自動管理所有這些操作,因爲它在觀察時可以感知相關的生命週期狀態變化。
  • 數據始終保持最新狀態
    • 如果生命週期變爲非活躍狀態,它會在再次變爲活躍狀態時接收最新的數據。例如,曾經在後臺的 Activity 會在返回前臺後立即接收最新的數據。
  • 適當的配置更改
    • 如果由於配置更改(如設備旋轉)而重新創建了 Activity 或 Fragment,它會立即接收最新的可用數據

就寫這些吧,突然降溫給我整感冒了,大家保護好自己!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章