【Android】使用ViewModel替代Fragment处理屏幕旋转时的状态恢复

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替代了。

发布了116 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/105246639
今日推荐