Fragivity如何处理BackStack的销毁重建

在这里插入图片描述

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值