背景
activity在配置變化(未配置configChanges)、內(nèi)存回收等情況下,頁面會重建。頁面在重新走一遍生命周期后,系統(tǒng)會幫我們恢復好控件的狀態(tài),具體原理可以看看《之前發(fā)布的文章》。
Activty、Fragment里定義的全局變量,需要我們手寫保存
- 在
onSaveInstanceState里把變量保存到Bundle - 在
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ù)。

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嵌套的情況,Fragment的ViewModel聲明不要在onCreate里!,會導致銷毀重建數(shù)據(jù)無效,原因和ViewModel的恢復順序有關(guān)。Fragment放在的onCreateView、onViewCreated里親測可以
問題1:為什么ViewModel可以在Activity、Fragment里共享數(shù)據(jù)?
ViewModel存儲在ViewModelStore類里。由ViewModelStoreOwner在Activity、Fragment里提供。
我們創(chuàng)建ViewModel的時候,ViewModelStoreOwner指向Activity的那份即可。
ViewModelProvider(activity, SavedStateViewModelFactory(null, this)).get( AnswerViewModel::class.java)