背景
上一篇文章[Navigation——Fragment创建新的实例问题],我们简述了我们在使用Navigation遇到的Fragment创建新的实例的问题。接下来,我们在这篇文章就来解决一下我们遇到的这个问题
源码追踪
打开 MainActivity 的布局文件,我们可以看到在布局文件当中, Frangmet 这里,有一个来自于 androidx的NavHostFragment。
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
复制代码
进入 NavHostFragment 的源码,我们一探究竟。 在 NavHostFragment 源码的 onCreate 方法当中,我们找到了答案。 完整的 onCreat 方法
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
mNavController = new NavController(context);
mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
Bundle navState = null;
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
requireFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
if (navState != null) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
}
复制代码
在这其中,有一行
mNavController = new NavController(context);
mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
复制代码
也就是说,只要添加一个 Fragment 就会在 NavController 当中 Add 一个 FragmentNavigator ,而在 createFragmentNavigator 方法当中,Navigator 方法里对 Fragment 进行了处理
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
}
复制代码
解决问题
既然,我们都已经找到导致没次都创建新的实例的根结所在,那么我们现在来解决一下问题。1那么我们只需要重新写一个NavHostFragment的createFragmentNavigator的方法,来满足我们的要求。
/**
* 复用的NavHostFragment (默认不是复用 引起一个问题就是 不保存fragment状态)
*/
class TabNavHostFragment : NavHostFragment() {
override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination> {
return MyNavigator(requireContext(), childFragmentManager, id)
}
//参考相关链接
// https://stackoverflow.com/questions/50485988/is-there-a-way-to-keep-fragment-alive-when-using-bottomnavigationview-with-new-n/51684125
@Navigator.Name("tab_fragment") // 这个名称在 navigation.xml 当中使用。
open class MyNavigator(var mContext: Context, var mFragmentManager: FragmentManager, var mContainerId: Int) :
FragmentNavigator(mContext, mFragmentManager, mContainerId) {
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
try {
//反射获取mBackStack mIsPendingBackStackOperation
val mBackStackField = FragmentNavigator::class.java.getDeclaredField("mBackStack")
mBackStackField.isAccessible = true
var mBackStack: ArrayDeque<Int> = mBackStackField.get(this) as ArrayDeque<Int>
val mIsPendingBackStackOperationField =
FragmentNavigator::class.java.getDeclaredField("mIsPendingBackStackOperation")
mIsPendingBackStackOperationField.isAccessible = true
var mIsPendingBackStackOperation: Boolean = mIsPendingBackStackOperationField.get(this) as Boolean
if (mFragmentManager.isStateSaved) {
//Log.i("TAG", "Ignoring navigate() call: FragmentManager has already" + " saved its state")
return null
}
var className = destination.className
if (className[0] == '.') {
className = mContext.packageName + className
}
val ft = mFragmentManager.beginTransaction()
var enterAnim = navOptions?.enterAnim ?: -1
var exitAnim = navOptions?.exitAnim ?: -1
var popEnterAnim = navOptions?.popEnterAnim ?: -1
var popExitAnim = navOptions?.popExitAnim ?: -1
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = if (enterAnim != -1) enterAnim else 0
exitAnim = if (exitAnim != -1) exitAnim else 0
popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
popExitAnim = if (popExitAnim != -1) popExitAnim else 0
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
val tag = destination.id.toString()
//ft.replace(mContainerId, frag)
val currentFragment = mFragmentManager.primaryNavigationFragment
if (currentFragment != null) {
ft.hide(currentFragment)
}
var frag = mFragmentManager.findFragmentByTag(tag)
if (frag == null) {
frag = instantiateFragment(
mContext, mFragmentManager,
className, args
)
frag.arguments = args
ft.add(mContainerId, frag, tag)
} else {
ft.show(frag)
}
ft.setPrimaryNavigationFragment(frag)
@IdRes val destId = destination.id
val initialNavigation = mBackStack.isEmpty()
// TODO Build first class singleTop behavior for fragments
val isSingleTopReplacement = (navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast().toInt() == destId)
val isAdded: Boolean
if (initialNavigation) {
isAdded = true
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateMyBackStackName(mBackStack.size, mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
ft.addToBackStack(generateMyBackStackName(mBackStack.size, destId))
mIsPendingBackStackOperation = true
mIsPendingBackStackOperationField.set(this, true)
}
isAdded = false
} else {
ft.addToBackStack(generateMyBackStackName(mBackStack.size + 1, destId))
mIsPendingBackStackOperation = true
mIsPendingBackStackOperationField.set(this, true)
isAdded = true
}
if (navigatorExtras is Extras) {
val extras = navigatorExtras as Extras?
for ((key, value) in extras!!.sharedElements) {
ft.addSharedElement(key, value)
}
}
ft.setReorderingAllowed(true)
ft.commit()
// The commit succeeded, update our view of the world
return if (isAdded) {
mBackStack.add(destId)
destination
} else {
null
}
} catch (e: Throwable) {
return super.navigate(destination, args, navOptions, navigatorExtras)
}
}
private fun generateMyBackStackName(backStackIndex: Int, destId: Int): String {
return "$backStackIndex-$destId"
}
}
}
复制代码
然后,在我们的代码当中,引入我们自定修改之后的这个 TabNavHostFragment 在 MianActivity 的布局文件当中修改为
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_nav_host_fragment"
android:name="com.demo.navigationcomponent.TabNavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
复制代码
然后,在 nav_graph.xml文件当中,修改为:
<tab_fragment android:id="@+id/oneFragment" android:name="com.demo.navigationcomponent.OneFragment"
android:label="fragment_one" tools:layout="@layout/fragment_one">
<action android:id="@+id/action_oneFragment_to_twoFragment"
app:destination="@id/twoFragment"
app:popUpTo="@id/oneFragment"
app:popUpToInclusive="true"/>
</tab_fragment>
复制代码
至此,我们大功告成了,当我们在添加新的 Fragment 的时候,当已经创建过 Fragment 的实例的时候,就不会创建新的实例了。
最后
通过以上的方法,可以实现我们想要的效果,但是我认为这只是一个临时的解决方案,修改源码这种方式,并不是一个特别好的解决方案。如果有其他更好的方法,欢迎给我留言!
文末送福利啦!!
同时我经过多年的收藏目前也算收集到了一套完整的学习资料以及高清详细的Android架构进阶学习导图及笔记免费分享给大家,希望对想成为架构师的朋友有一定的参考和帮助。 **下面是部分资料截图,诚意满满:特别适合有开发经验的Android程序员们学习。
资料免费领取方式:现在关注我并且加入群聊 群号:1018342383 或者是点击链接加入群聊【Android开发交流】:jq.qq.com/?_wv=1027&a…