Android Jetpack 組件之 ViewModel(Kotlin)

簡介

Android 的頁面在創建與銷燬時,會觸發不同的生命週期。當 Activity 重建時,頁面上的數據會丟失。爲了保存頁面的數據,我們以前通常的做法是在 onSaveInstanceState 中,將數據保存到 bundle 中,再在 onCreate 中將 bundle 中的數據取出來。
現在有了 ViewModel,我們就無需再用這種方法保存,因爲 ViewModel 會自動感知生命週期,處理數據的保存與恢復。引用一張官網的介紹圖:

導入

android {
    ...
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

dependencies {
    ...
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    implementation 'androidx.fragment:fragment-ktx:1.2.4'
}

ViewModel 的使用

新建 MyViewModel 類,繼承自 ViewModel

class MyViewModel : ViewModel() {
    var number = 0
}

修改 MainActivity

佈局文件中只有一個 id 爲 tv 的 TextView 和一個 id 爲 btn 的 Button,故省略佈局文件。

class MainActivity : AppCompatActivity() {

    private val myViewModel by viewModels<MyViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv.text = myViewModel.number.toString()
        btn.setOnClickListener {
            myViewModel.number++
            tv.text = myViewModel.number.toString()
        }
    }
}

運行效果

viewmodel

對比不使用 ViewModel 的情況

如果不使用 ViewModel ,只用一個 number 變量保存數據的話,頁面重建時數據將會丟失,運行效果如下:

ViewModel 的侷限性

當進程在後臺被系統殺死後,ViewModel 裏的數據還是會丟失。爲了模擬這個情景,我們先到開發者選項中將 Don't keep activities 打開:

這個設置打開的作用是,當我們點擊 home 鍵使程序進入後臺時,程序會立刻被系統殺掉。
這時我們再次運行以上使用 ViewModel 的程序,效果如下:

可以看到,當進程被系統回收後,ViewModel 中的數據丟失了。爲了解決這個問題,我們需要用到 ViewModelSavedState.

ViewModelSavedState

導入 ViewModelSavedState

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"

修改 MyViewModel

導入依賴後,我們就多了一個構造方法,允許 ViewModel 傳入一個 SavedStateHandle 類。這個類的內部使用了一個 HashMap 保存數據。

const val NUMBER_KEY = "number_key"

class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
    var number: Int
        get() {
            return state.get<Int>(NUMBER_KEY) ?: 0
        }
        set(value) {
            state[NUMBER_KEY] = value
        }
}

可以看到,我們在 number 的 get 方法中,通過 SavedStateHandle 獲取 number,如果沒有獲取到,默認返回 0;在 set 方法中,修改 state 中對應的值。

修改 MainActivity

class MainActivity : AppCompatActivity() {

    private val myViewModel by lazy {
        ViewModelProvider(this, SavedStateViewModelFactory(application, this)).get(MyViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv.text = myViewModel.number.toString()
        btn.setOnClickListener {
            myViewModel.number++
            tv.text = myViewModel.number.toString()
        }
    }
}

唯一的修改在於 myViewModel 的初始化,不妨記做固定寫法。

再次運行程序,數據就不會丟失了。

需要注意的是,這裏的數據也不是永久保存的,當手機重啓或者用戶手動殺掉進程後,數據仍然會丟失。

如果需要持久化存儲,可以使用 SharedPreferences或數據庫將其存儲起來。
當調用這些存儲方法時,往往我們都會用到 Context,所以 Android 給我們提供了一個 AndroidViewModel 類。

AndroidViewModel

AndroidViewModel 類做的事情很簡單,就是封裝了一個 Application 字段。
源碼如下:

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

使用如下:

class MyViewModel(application: Application) : AndroidViewModel(application) {
    fun test() {
        Log.d("~~~", getApplication<Application>().resources.getString(R.string.app_name))
    }
}

只需繼承 AndroidViewModel,我們就可以通過 getApplication<Application> 來獲取到 Application 對象。
Activity 中對 ViewModel 的初始化、使用還是和以前一樣,完全不用修改。

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