美文网首页
Activity重建时恢复数据

Activity重建时恢复数据

作者: 紫阚 | 来源:发表于2020-10-19 10:49 被阅读0次

    背景

    activity在配置变化(未配置configChanges)、内存回收等情况下,页面会重建。页面在重新走一遍生命周期后,系统会帮我们恢复好控件的状态,具体原理可以看看《之前发布的文章》

    Activty、Fragment里定义的全局变量,需要我们手写保存

    1. onSaveInstanceState里把变量保存到Bundle
    2. onCreate里判断savedInstanceState不为空,则进行恢复。示例代码如下
    class ManualSaveActivity : AppCompatActivity() {
        lateinit var mPerson: Person
        val PARAM_SAVE_KEY = "PARAM_KEY"
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_manual_save)
    
    
            mPerson = if (savedInstanceState != null) {
                //从bundle恢复数据
                savedInstanceState.getSerializable(PARAM_SAVE_KEY) as Person
            } else {
                //否则走默认的初始化流程
                Person("桑德兰", 18, null)
            }
    
        }
    
        override fun onSaveInstanceState(outState: Bundle) {
            super.onSaveInstanceState(outState)
            //保存数据
            outState.putSerializable(PARAM_SAVE_KEY, mPerson)
        }
    }
    

    这种写法可以满足重建恢复数据的需求,但我们希望能更进一步:通过框架,可以智能实现数据的保存、恢复,业务代码不需要关心这个过程。

    解决方案

    Android的jetPack架构包里,ViewModel原生就支持配置变化自动重建,再通过SavedStateHandle扩展就可以在内存回收的时候,也能保存数据了。

    ViewModel+SavedStateHandle创建对象非常简单

    ViewModelProvider(载体, SavedStateViewModelFactory(null, this)).get(AnswerViewModel::class.java)
    

    只要activity、fragment载体都指向activity,就可以在fragment共享数据。
    SavedStateViewModelFactory是能在内存回收的时候保存数据关键,本质上也是通过Bundle保存、恢复数据。

    开发实践

    我们选择ViewModel+SavedStateHandle这个组合,写一个计数器DEMO进行实践;为了展示ViewModel能在Fragment之间共享Activity数据的能力,我们的结构是Activity壳嵌套了Fragment
    数据流转过程是:Activity请求数据,Fragment修改数据。

    点击文本,年龄+1

    ViewModel定义

    可以看到实际存储,是交给了SavedStateHandle,开发者只用关心存取就可以

    data class Person(
        var name: String,
        var age: Int,
        var answerPosList:List<Int>?
    ):Serializable
    
    class AnswerViewModel(private val savedStateHandler: SavedStateHandle) : ViewModel() {
        private val KEY_PERSON = "KEY_PERSON"
    
        fun setPerson(person: Person) {
            savedStateHandler.set(KEY_PERSON, person)
        }
    
        fun getPerson(): Person? {
            return savedStateHandler.get<Person>(KEY_PERSON)
        }
    }
    

    Activity代码

    class SimpleActivity : AppCompatActivity() {
        private lateinit var model: AnswerViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            mock(savedInstanceState)
            setContentView(R.layout.activity_simple)
    
            object : FragmentSwitcher<Int, Fragment>(supportFragmentManager, R.id.fl_simple_root) {
                override fun generateFragment(key: Int?): Fragment {
                    return BlankFragment.newInstance(key.toString())
                }
            }.changeFragment(0)
        }
    
        //初始化
        private fun mock(savedInstanceState: Bundle?) {
            model = ViewModelProvider(this, SavedStateViewModelFactory(null, this)).get(
                AnswerViewModel::class.java
            )
    
            if (model.getPerson() == null) {
                Log.d("测试", "初始化")
                val person = Person("桑德兰", 18, null)
                model.setPerson(person)
            } else {
                Log.d("测试", "不需要初始化" + model.getPerson())
            }
        }
    
        companion object {
            @JvmStatic
            fun start(context: Context) {
                val starter = Intent(context, SimpleActivity::class.java)
                context.startActivity(starter)
            }
        }
    }
    

    Fragment核心代码

    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            mViewModel = activity?.let {
                ViewModelProvider(it, SavedStateViewModelFactory(null, this)).get(
                    AnswerViewModel::class.java
                )
            }!!
    
            tvBind = view.findViewById(R.id.tv_blank_hello)
            tvBind?.setOnClickListener(this)
            renderUser()
        }
        override fun onClick(v: View?) {
            val id = v?.id
    
            if (id == R.id.tv_blank_hello) {
                mViewModel.getPerson()?.age=  mViewModel.getPerson()!!.age+1
                renderUser()
            }
        }
    
    
        private fun renderUser() {
            tvBind?.text = "第${mArgPos}页,姓名:${mViewModel.getPerson()?.name} \n 年龄:${mViewModel.getPerson()?.age}"
        }
    
    

    流程分析

    系统SavedStateHandle大致流程和我们手写保存一样。

    1.onSaveInstanceState的时候保存到系统的bundle
    保存流程
    performSave 被Activity、Fragment调用

    androidx.activity.ComponentActivity

       @CallSuper
        @Override
        protected void onSaveInstanceState(@NonNull Bundle outState) {
            Lifecycle lifecycle = getLifecycle();
            if (lifecycle instanceof LifecycleRegistry) {
                ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
            }
            super.onSaveInstanceState(outState);
            mSavedStateRegistryController.performSave(outState);
        }
    
    

    Fragment

        void performSaveInstanceState(Bundle outState) {
            onSaveInstanceState(outState);
            mSavedStateRegistryController.performSave(outState);
            Parcelable p = mChildFragmentManager.saveAllState();
            if (p != null) {
                outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p);
            }
        }
    
    2. onCreate的时候根据savedInstanceState恢复。
    恢复流程
    performRestore 被Activity、Fragment调用

    androidx.activity.ComponentActivity

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mSavedStateRegistryController.performRestore(savedInstanceState);
            ReportFragment.injectIfNeededIn(this);
            if (mContentLayoutId != 0) {
                setContentView(mContentLayoutId);
            }
        }
    

    Fragment

     void performCreate(Bundle savedInstanceState) {
            mChildFragmentManager.noteStateNotSaved();
            mState = CREATED;
            mCalled = false;
            mSavedStateRegistryController.performRestore(savedInstanceState);
            onCreate(savedInstanceState);
            mIsCreated = true;
            if (!mCalled) {
                throw new SuperNotCalledException("Fragment " + this
                        + " did not call through to super.onCreate()");
            }
            mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
        }
    

    tips

    遇到fragmentactivity嵌套的情况,FragmentViewModel声明不要在onCreate里!,会导致销毁重建数据无效,原因和ViewModel的恢复顺序有关。Fragment放在的onCreateViewonViewCreated里亲测可以

    问题1:为什么ViewModel可以在Activity、Fragment里共享数据?
    ViewModel存储在ViewModelStore类里。由ViewModelStoreOwnerActivity、Fragment里提供。
    我们创建ViewModel的时候,ViewModelStoreOwner指向Activity的那份即可。

    ViewModelProvider(activity, SavedStateViewModelFactory(null, this)).get( AnswerViewModel::class.java)

    相关文章

      网友评论

          本文标题:Activity重建时恢复数据

          本文链接:https://www.haomeiwen.com/subject/cgkwpktx.html