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 的初始化、使用还是和以前一样,完全不用修改。

原创文章 67 获赞 68 访问量 6万+

猜你喜欢

转载自blog.csdn.net/AlpinistWang/article/details/105747675