背景
activity
在配置变化(未配置configChanges)、内存回收等情况下,页面会重建。页面在重新走一遍生命周期后,系统会帮我们恢复好控件的状态,具体原理可以看看《之前发布的文章》。
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恢复数据
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
修改数据。
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
遇到fragment
、activity
嵌套的情况,Fragment
的ViewModel
声明不要在onCreate
里!,会导致销毁重建数据无效,原因和ViewModel
的恢复顺序有关。Fragment
放在的onCreateView
、onViewCreated
里亲测可以
问题1:为什么ViewModel
可以在Activity、Fragment
里共享数据?
ViewModel
存储在ViewModelStore
类里。由ViewModelStoreOwner
在Activity、Fragment
里提供。
我们创建ViewModel的时候,ViewModelStoreOwner
指向Activity的那份即可。
ViewModelProvider(activity, SavedStateViewModelFactory(null, this)).get( AnswerViewModel::class.java)