Activity重建時恢復數(shù)據(jù)

背景

activity在配置變化(未配置configChanges)、內(nèi)存回收等情況下,頁面會重建。頁面在重新走一遍生命周期后,系統(tǒng)會幫我們恢復好控件的狀態(tài),具體原理可以看看《之前發(fā)布的文章》。

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恢復數(shù)據(jù)
            savedInstanceState.getSerializable(PARAM_SAVE_KEY) as Person
        } else {
            //否則走默認的初始化流程
            Person("桑德蘭", 18, null)
        }

    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        //保存數(shù)據(jù)
        outState.putSerializable(PARAM_SAVE_KEY, mPerson)
    }
}

這種寫法可以滿足重建恢復數(shù)據(jù)的需求,但我們希望能更進一步:通過框架,可以智能實現(xiàn)數(shù)據(jù)的保存、恢復,業(yè)務代碼不需要關(guān)心這個過程。

解決方案

Android的jetPack架構(gòu)包里,ViewModel原生就支持配置變化自動重建,再通過SavedStateHandle擴展就可以在內(nèi)存回收的時候,也能保存數(shù)據(jù)了。

ViewModel+SavedStateHandle創(chuàng)建對象非常簡單

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

只要activity、fragment載體都指向activity,就可以在fragment共享數(shù)據(jù)。
SavedStateViewModelFactory是能在內(nèi)存回收的時候保存數(shù)據(jù)關(guān)鍵,本質(zhì)上也是通過Bundle保存、恢復數(shù)據(jù)。

開發(fā)實踐

我們選擇ViewModel+SavedStateHandle這個組合,寫一個計數(shù)器DEMO進行實踐;為了展示ViewModel能在Fragment之間共享Activity數(shù)據(jù)的能力,我們的結(jié)構(gòu)是Activity殼嵌套了Fragment。
數(shù)據(jù)流轉(zhuǎn)過程是:Activity請求數(shù)據(jù),Fragment修改數(shù)據(jù)。

點擊文本,年齡+1

ViewModel定義

可以看到實際存儲,是交給了SavedStateHandle,開發(fā)者只用關(guān)心存取就可以

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}"
    }

流程分析

系統(tǒng)SavedStateHandle大致流程和我們手寫保存一樣。

1.onSaveInstanceState的時候保存到系統(tǒng)的bundle
保存流程
performSave 被Activity、Fragment調(diào)用

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的時候根據(jù)savedInstanceState恢復。
恢復流程
performRestore 被Activity、Fragment調(diào)用

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

遇到fragment、activity嵌套的情況,FragmentViewModel聲明不要在onCreate里!,會導致銷毀重建數(shù)據(jù)無效,原因和ViewModel的恢復順序有關(guān)。Fragment放在的onCreateViewonViewCreated里親測可以

問題1:為什么ViewModel可以在Activity、Fragment里共享數(shù)據(jù)?
ViewModel存儲在ViewModelStore類里。由ViewModelStoreOwnerActivity、Fragment里提供。
我們創(chuàng)建ViewModel的時候,ViewModelStoreOwner指向Activity的那份即可。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容