Activity销毁重建时的状态恢复是Android开发中经常遇到的问题:
假设在Activity中启动了一个AsyncTask,然后用户马上旋转屏幕,这会触发 Configuration Changes从而导致Activity被销毁和重新创建。当AsyncTask最后完成它的任务,它会将结果反馈到旧的Activity实例,完全没有意识到新的activity已经被创建了。
此时首先想到的可能是通过在Android manifest中设置android:configChanges
属性禁止默认的销毁和重新创建行为。然而Google的工程师建议不这么做。主要的担忧是:需要你在代码中手动处理设备的配置变化,以确保每一个字符串、布局、绘图、尺寸等与当前设备的配置一致。
那还有其他什么解决方案吗?以往我们常用的一个解决方案是使用Retained Fragment,而现在我们可以用ViewModel来解决。
接下来对比一下两种处理方式的不同:
A.使用Fragment
默认情况下,当配置发生变化时Fragment会随着它们的宿主Activity被创建和销毁。调用Fragment#setRetaininstance(true)
允许我们跳过销毁和重新创建的周期。此时可以让fragment持有像运行中的线程、AsyncTask、Socket等对象将有效地解决上面的问题。
class MainActivity : FragmentActivity(), TaskCallbacks {
private var mTaskFragment: TaskFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
val fm: FragmentManager = supportFragmentManager
mTaskFragment = fm.findFragmentByTag(TAG_TASK_FRAGMENT) as TaskFragment
if (mTaskFragment == null) {//如果Fragment不为null,那么它就是在配置变化的时候被保存下来的
mTaskFragment = TaskFragment()
fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit()
}
}
//来自Fragment的回调
override fun onPreExecute() {}
override fun onProgressUpdate(percent: Int) {}
override fun onCancelled() {}
override fun onPostExecute() {}
companion object {
private const val TAG_TASK_FRAGMENT = "task_fragment"
}
}
/**
* 让Fragment通知Activity任务进度和返回结果的回调接口
*/
internal interface TaskCallbacks {
fun onPreExecute()
fun onProgressUpdate(percent: Int)
fun onCancelled()
fun onPostExecute()
}
class TaskFragment : Fragment() {
private var mCallbacks: TaskCallbacks? = null
private var mTask = DummyTask()
override fun onAttach(activity: Activity?) {
super.onAttach(activity)
mCallbacks = activity as TaskCallbacks
}
/**
* 这个方法只会被调用一次,只在这个被保存Fragment第一次被创建的时候
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setRetainInstance(true)//在配置变化的时候将这个fragment保存下来
mTask = DummyTask()
mTask.execute()
}
override fun onDetach() {
super.onDetach()
mCallbacks = null
}
private open inner class DummyTask : AsyncTask<Void?, Int?, Void?>() {
override fun onPreExecute() {
if (mCallbacks != null) {
mCallbacks!!.onPreExecute()
}
}
override fun doInBackground(vararg ignore: Void?): Void? {
var i = 0
while (!isCancelled() && i < 100) {
SystemClock.sleep(100)
publishProgress(i)
i++
}
return null
}
override fun onProgressUpdate(vararg value: Int) {
if (mCallbacks != null) {
mCallbacks!!.onProgressUpdate(value[0])
}
}
override fun onCancelled() {
if (mCallbacks != null) {
mCallbacks!!.onCancelled()
}
}
override fun onPostExecute(ignore: Void?) {
if (mCallbacks != null) {
mCallbacks!!.onPostExecute()
}
}
}
}
B.使用ViewModel
The ViewModel class allows data to survive configuration changes such as screen rotations.
ViewModel天生就是为了屏幕旋转等场景中的状态保存而创建的。上面代码用ViewModel处理的效果如下:
class MainActivity : FragmentActivity() , TaskCallbacks{
private val vm by lazy {
//Activity重建后,依然可以通过context获取之前的ViewModel
ViewModelProviders.of(this).get(TaskViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
vm.registerCallback(this)
}
override fun onDestroy() {
super.onDestroy()
vm.unregisterCallback()
}
override fun onPreExecute() {}
override fun onProgressUpdate(percent: Int) {}
override fun onCancelled() {}
override fun onPostExecute() {}
}
class TaskViewModel : ViewModel() {
private var mCallbacks: TaskCallbacks? = null
private var mTask = DummyTask()
init {
mTask.execute()
}
internal fun startTask() {
mTask.execute()
}
internal fun registerCallback(cb: TaskCallbacks) {
mCallbacks = cb
}
internal fun unregisterCallback() {
mCallbacks = null
}
private open inner class DummyTask : AsyncTask<Void?, Int?, Void?>() {
...
}
}
Activity重建时,系统会把上次销毁的Activity的内部ViewModelStore
中的所有ViewModel传给新的Activity的ViewModelStore,新的Activity中通过ViewModelProvides.of
可以从ViewModelStore中获取之前的ViewModel。
从上图可以看到ViewModel与Activity的声明周期的对应关系。Activity最终finished的时,会通过ViewModelStore的onCleared
清空当前的ViewModel,当然横竖屏切换这种情况Activity在finished
志之前已经将ViewModel传递给新的Activity的ViewModelStore中了,不会有问题。
另外插一句,即使进程重启,前次的ViewModel被onCleared了,我们仍然可以像Activity的onRestoreInstanceState
一样通过ViewModel获取之前的状态。虽然数据可以恢复,但像本例中的异步任务肯定是无法恢复了,篇幅有限这里就不具体介绍了,有兴趣可以参考
https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate
使用LiveData替代回调
除了用ViewModel替代Fragment以外,例子中TaskCallbacks
这种异步回调的方式也有替代方案:我们可以使用LiveData
进行订阅,如下:
class MainActivity : FragmentActivity(), TaskCallbacks {
private val vm by lazy {
ViewModelProviders.of(this).get(TaskViewModel::class.java)
}
private val observer = object: Observer<Int> {
override fun onChanged(t: Int?) {
...
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
vm.liveData.observe(observer) {
...
}
}
override fun onDestroy() {
super.onDestroy()
vm.liveData.removeObserver(observer)
}
}
class TaskViewModel : ViewModel() {
val liveData: LiveData<Int>
get() = _liveData
private val _liveData = MutableLiveData<Int>()
init {
start()
}
private fun start() {
Executors.newFixedThreadPool(1).execute {
var i = 0
while ( i < 100) {
SystemClock.sleep(100)
runOnUIThread {
_liveData.value = i
}
i++
}
}
}
}
LiveData相对于一般Callback的好处是,它是生命周期有感知的,当Activity处于后台时,将不会受到回调,避免无意义的UI刷新。
总结
上面例子中AsyncTask的异步处理其实也可以使用RxJava来替代,这样一来传统的Fragment+Async+Callback
的组合完全可以由ViewModel+LiveData+RxJava
替代了。
关于ViewModel的实现原理可以参考 源码分析ViewModel如何实现横竖屏切换时不销毁