Fragivity是一个基于Fragment打造的单Activity框架。
销毁重建的问题
Fragivity在Navigation基础上实现了免配置的路由跳转:无需事先配置NavGraph,在跳转时再动态更新Graph,这在方便了使用的同时带来一个问题:
FragmentManager#mBackStack
FragmentManger
一样维护了一个mBackStack
,当Activity恢复重建时,会对BackStack重建以恢复之前的栈状态
//FragmentManager.java
void restoreSaveState(@Nullable Parcelable state) {
// If there is no saved state at all, then there's nothing else to do
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState) state;
if (fms.mActive == null) return;
...
// Build the back stack.
if (fms.mBackStack != null) {
mBackStack = new ArrayList<>(fms.mBackStack.length);
for (int i = 0; i < fms.mBackStack.length; i++) {
BackStackRecord bse = fms.mBackStack[i].instantiate(this);
mBackStack.add(bse);
}
}
...
}
NavController#mBackStack
Navigation在NavController
中同样维护了一个mBackStack
,管理栈内的Destination。为了保证与FragmentManager中的BackStack一致性,NavController借助mBackStackToRestore
销毁重建
//NavController.java
public void restoreState(@Nullable Bundle navState) {
...
mBackStackToRestore = navState.getParcelableArray(KEY_BACK_STACK);
...
}
//NavController.java
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
...
if (mBackStackToRestore != null) {
for (Parcelable parcelable : mBackStackToRestore) {
NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
NavDestination node = findDestination(state.getDestinationId());
if (node == null) {
...
}
Bundle args = state.getArgs();
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
mLifecycleOwner, mViewModel,
state.getUUID(), state.getSavedState());
mBackStack.add(entry);
}
}
...
}
如上,恢复重建时需要根据state信息重建Destination
NavDestination node = findDestination(state.getDestinationId());
findDestination内部需要在NavGraph查询节点信息。对于Navigation来说,因为有实现配置,Activity重启后会根据Xml或者Dsl重建Graph,但是对于Fragivity来说,Graph是动态更新的,此时无法成功findDestination。
使用ViewModel保存Destination
Activity重启时,我们需要在Graph中能找到所有BackStack重建必要的Destination信息。我们需要将入栈的Destination信息保存到重建后并恢复到Graph中,很自然的想到使用ViewModel跨越Framgent生命周期的特性来实现。
internal class MyViewModel : ViewModel() {
val nodes = SparseArrayCompat<NavDestination>()
}
class MyNavHost : NavHost {
...
internal fun saveToViewModel(destination: NavDestination) {
val vm = ViewModelProvider(requireActivity())[MyViewModel::class.java]
if (vm.nodes.keyIterator().asSequence().any {
it == destination.id
}) return
vm.nodes.put(destination.id, destination)
}
...
}
Fragment压栈的同时记录到ViewModel
internal fun MyNavHost.putFragment(
clazz: KClass<out Fragment>
): FragmentNavigator.Destination {
val destId = clazz.hashCode()
val graph = navController.graph
var destination = graph.findNode(destId) as? FragmentNavigator.Destination
if (destination == null) {
destination = navController.createNavDestination(destId, clazz)
graph.plusAssign(destination)
saveToViewModel(destination)
}
return destination
}
当恢复重建时,从ViewModel恢复Destination信息到Graph
fun NavHostFragment.loadRoot(root: KClass<out Fragment>) {
...
graph = createGraph(startDestination = startDestId) {
...
}.also { graph ->
//restore destination from vm for NavController#mBackStackToRestore
val activity = requireActivity()
val vm = ViewModelProvider(activity ).get(MyViewModel::class.java)
vm.nodes.valueIterator().forEach {
it.removeFromParent()
graph += it
}
}
}
ViewModel持久化
通过ViewModel虽然可以解决Configurations Change
之后的销毁重建,但是对于进程被杀的销毁重建仍然无效。例如https://github.com/vitaviva/fragivity/issues/9
跨进程保存需要借助ViewModel的持久化机制,改造如下:
internal class MyViewModel(private val _handle: SavedStateHandle) : ViewModel() {
lateinit var navController: NavController
val nodes by lazy {
val list: List<NavDestinationBundle> =
_handle.get<Bundle>(NAV_DEST_NODES_KEY)?.getParcelableArrayList(null) ?: emptyList()
val nodes = SparseArrayCompat<NavDestination>()
list.forEach {
val clazz = Class.forName(it.className) as Class<Fragment>
val destination = navController.createNavDestination(clazz.hashCode(), clazz.kotlin)
nodes.put(destination.id, destination)
}
nodes
}
init {
/**
* 当杀进程重启(例如不保留活动等)时,NavGraph中的nodes信息也需要重建,
* 所以需要借助SavedStateHandle对ViewModel数据持久化
*/
_handle.setSavedStateProvider(NAV_DEST_NODES_KEY) {
val list = ArrayList<NavDestinationBundle>().apply {
nodes.valueIterator().forEach { add(NavDestinationBundle((it))) }
}
Bundle().apply { putParcelableArrayList(null, list) }
}
}
}
然后借助SavedStateViewModelFactory
从ViewModel恢复持久化信息
fun NavHostFragment.loadRoot(root: KClass<out Fragment>) {
...
graph = createGraph(startDestination = startDestId) {
...
}.also { graph ->
//restore destination from vm for NavController#mBackStackToRestore
val activity = requireActivity()
val vm = ViewModelProvider(
activity,
SavedStateViewModelFactory(activity.application, activity)
).get(MyViewModel::class.java).also {
it.navController = this
}
vm.nodes.valueIterator().forEach {
it.removeFromParent()
graph += it
}
}
}