欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack 组件之 ViewModel(Kotlin)

程序员文章站 2022-06-07 16:53:30
...

简介

Android 的页面在创建与销毁时,会触发不同的生命周期。当 Activity 重建时,页面上的数据会丢失。为了保存页面的数据,我们以前通常的做法是在 onSaveInstanceState 中,将数据保存到 bundle 中,再在 onCreate 中将 bundle 中的数据取出来。
现在有了 ViewModel,我们就无需再用这种方法保存,因为 ViewModel 会自动感知生命周期,处理数据的保存与恢复。引用一张官网的介绍图:
Android Jetpack 组件之 ViewModel(Kotlin)

导入

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()
        }
    }
}

运行效果

Android Jetpack 组件之 ViewModel(Kotlin)

对比不使用 ViewModel 的情况

如果不使用 ViewModel ,只用一个 number 变量保存数据的话,页面重建时数据将会丢失,运行效果如下:
Android Jetpack 组件之 ViewModel(Kotlin)

ViewModel 的局限性

当进程在后台被系统杀死后,ViewModel 里的数据还是会丢失。为了模拟这个情景,我们先到开发者选项中将 Don't keep activities 打开:
Android Jetpack 组件之 ViewModel(Kotlin)
这个设置打开的作用是,当我们点击 home 键使程序进入后台时,程序会立刻被系统杀掉。
这时我们再次运行以上使用 ViewModel 的程序,效果如下:
Android Jetpack 组件之 ViewModel(Kotlin)
可以看到,当进程被系统回收后,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 的初始化,不妨记做固定写法。

再次运行程序,数据就不会丢失了。
Android Jetpack 组件之 ViewModel(Kotlin)
需要注意的是,这里的数据也不是永久保存的,当手机重启或者用户手动杀掉进程后,数据仍然会丢失。
Android Jetpack 组件之 ViewModel(Kotlin)
如果需要持久化存储,可以使用 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 的初始化、使用还是和以前一样,完全不用修改。