Jetpack系列-Navigation使用和源码分析

1 简介

Navigation是Android Jetpack中的一个框架,用于在Android应用中的“目标”之间导航,该框架提供一致的 API,“目标”可以是Fragment、Activity或者其他组件。

导航组件由以下三个关键部分组成:

  • 导航图: 在一个集中位置包含所有导航相关信息的XML资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径。
  • NavHost:显示导航图中目标的空白容器。导航组件包含一个默认NavHost实现 (NavHostFragment),可显示Fragment目标。
  • NavController:NavHost中管理应用导航的对象。当用户在整个应用中移动时,NavController会安排NavHost中目标内容的交换。

导航组件提供各种其他优势,包括以下内容:

  • 处理Fragment事务。
  • 默认情况下,正确处理往返操作。
  • 为动画和转换提供标准化资源。
  • 实现和处理深层链接。
  • 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
  • Safe Args - 可在目标之间导航和传递数据时提供类型安全的Gradle插件。
  • ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
  • 可以使用Android Studio的Navigation Editor来查看和编辑导航图。

Google Developers 文档:https://developer.android.google.cn/guide/navigation

2 示例代码

以下示例代码是在Android Studio创建的模板代码Bottom Navigation Activity下修改的。

UI就是下图的样子,很简单,1个Activity,3个Fragment。点击HomeFragment中的按钮,能跳转到一个DetailFragment。

首先看activity_main.xml,里边有一个BottomNavigationView来防止底部的3个tab。上边一个fragment标签来放置Fragment

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment标签中引入了NavHostFragment作为主导航,NavHostFragment里能获取到NavController来管理控制其他Fragment

fragment标签有一个属性navGraphnavGraph是导航图,导航图文件mobile_navigation.xml如下所示:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="cn.zhangmushui.navigationsample.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_navigation_home_to_navigation_detail"
            app:destination="@id/navigation_detail" />
    </fragment>

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="cn.zhangmushui.navigationsample.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="cn.zhangmushui.navigationsample.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />

    <fragment
        android:id="@+id/navigation_detail"
        android:name="cn.zhangmushui.navigationsample.ui.detail.DetailFragment"
        android:label="@string/title_detail"
        tools:layout="@layout/fragment_detail" />
</navigation>

mobile_navigation.xml文件位于res/navigation。存放了4个FragmentHomeFragment下有一个action标签,表示跳转,action标签下的destination属性表示要跳转到的位置,这里是跳到DetailFragment

点击XML页面右上角的Design,可以切换到导航编辑界面,可以用鼠标按钮进行跳转、主Fragment修改,以及添加新的Fragment

在activity_main.xml中的BottomNavigationView标签下,menu属性引入了菜单文件bottom_nav_menu.xml,该文件位于res/menu下,每个item的id和mobile_navigation.xml中每个fragment的id一一对应:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

然后在Activity中,将BottomNavigationViewNavHostFragment关联起来即可:

class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        val navView: BottomNavigationView = binding.navView
        
        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }
}

而在HomeFragment中,点击按钮可以跳转到DetailFragment,调用的是Navigation.findNavController().navigate()navigate传入的id就是mobile_navigation.xml下fragment标签下的action标签的id。

class HomeFragment : Fragment() {

    private var _binding: FragmentHomeBinding? = null

    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val homeViewModel =
            ViewModelProvider(this).get(HomeViewModel::class.java)

        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val textView: TextView = binding.textHome
        homeViewModel.text.observe(viewLifecycleOwner) {
            textView.text = it
        }

        //跳转到详情页
        _binding?.btnDetail?.setOnClickListener {
            Navigation.findNavController(it)
                .navigate(R.id.action_navigation_home_to_navigation_detail)
        }

        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

其他几个类和文件可以下载完整源码查看:https://gitee.com/mushuicode/navigation-sample

3流程图

这是分析完源码之后画出的流程图,可以先看一遍流程图,然后再看下面的源码分析,看完源码之后再结合流程图加深印象。

1652438319712-99f4b86a-95b5-4446-96c7-317ee75844f7.png

4 源码分析

以下以navigation 2.4.2进行源码分析,navigation的源码稍微有点复杂,需要花费一定时间去读。

4.1 NavHostFragment.create()

NavHostFragment实例化的时候,会调用create方法,在create方法实例化NavHostFragment

public companion object {
    
    ...
    
    @JvmOverloads
    @JvmStatic
    public fun create(
        @NavigationRes graphResId: Int,
        startDestinationArgs: Bundle? = null
    ): NavHostFragment {
        var b: Bundle? = null
        if (graphResId != 0) {
            b = Bundle()
            b.putInt(KEY_GRAPH_ID, graphResId)
        }
        if (startDestinationArgs != null) {
            if (b == null) {
                b = Bundle()
            }
            b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs)
        }
        //实例化NavHostFragment
        val result = NavHostFragment()
        if (b != null) {
            //设置参数
            result.arguments = b
        }
        return result
    }
}

4.2 NavHostFragment.onInflate()

由于NavHostFragment是通过fragment标签引入,是一个静态Fragment,静态FragmentonInflate中初始化,所以NavHostFragment会先执行onInflate方法,该方法中会将fragment标签中的navGraph属性和defaultNavHost属性解析出来。

@CallSuper
public override fun onInflate(
    context: Context,
    attrs: AttributeSet,
    savedInstanceState: Bundle?
        ) {
    super.onInflate(context, attrs, savedInstanceState)
    context.obtainStyledAttributes(
        attrs,
        androidx.navigation.R.styleable.NavHost
    ).use { navHost ->
        //解析activity_main.xml中fragment标签下的 app:navGraph="@navigation/mobile_navigation"
        val graphId = navHost.getResourceId(
            androidx.navigation.R.styleable.NavHost_navGraph, 0
        )
        if (graphId != 0) {
            this.graphId = graphId
        }
    }
    //解析activity_main.xml中fragment标签下的 app:defaultNavHost="true"
    //将NavHostFragment设为默认显示的Fragment
    context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment).use { array ->
        val defaultHost = array.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false)
        if (defaultHost) {
            defaultNavHost = true
        }
    }
}

4.3 NavHostFragment.onCreate()

onCreate方法里实例化了一个NavHostController,并且传入了mGraphId

@CallSuper
public override fun onCreate(savedInstanceState: Bundle?) {
    var context = requireContext()
    //实例化一个NavHostController
    navHostController = NavHostController(context)
    navHostController!!.setLifecycleOwner(this)
    while (context is ContextWrapper) {
        if (context is OnBackPressedDispatcherOwner) {
            navHostController!!.setOnBackPressedDispatcher(
                (context as OnBackPressedDispatcherOwner).onBackPressedDispatcher
            )
            // Otherwise, caller must register a dispatcher on the controller explicitly
            // by overriding onCreateNavHostController()
            break
        }
        context = context.baseContext
    }
    // Set the default state - this will be updated whenever
    // onPrimaryNavigationFragmentChanged() is called
    navHostController!!.enableOnBackPressed(
        isPrimaryBeforeOnCreate != null && isPrimaryBeforeOnCreate as Boolean
    )
    isPrimaryBeforeOnCreate = null
    navHostController!!.setViewModelStore(viewModelStore)
    //向mNavController中添加Navigator
    onCreateNavHostController(navHostController!!)
    var navState: Bundle? = null
    if (savedInstanceState != null) {
        navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE)
        if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
            defaultNavHost = true
            parentFragmentManager.beginTransaction()
                .setPrimaryNavigationFragment(this)
                .commit()
        }
        graphId = savedInstanceState.getInt(KEY_GRAPH_ID)
    }
    if (navState != null) {
        // Navigation controller state overrides arguments
        navHostController!!.restoreState(navState)
    }
    if (graphId != 0) {
        // Set from onInflate()
        //设置mGraphId,解析mobile_navigation.xml中的信息
        navHostController!!.setGraph(graphId)
    } else {
        // See if it was set by NavHostFragment.create()
        val args = arguments
        val graphId = args?.getInt(KEY_GRAPH_ID) ?: 0
        val startDestinationArgs = args?.getBundle(KEY_START_DESTINATION_ARGS)
        if (graphId != 0) {
            navHostController!!.setGraph(graphId, startDestinationArgs)
        }
    }
    
    // We purposefully run this last as this will trigger the onCreate() of
    // child fragments, which may be relying on having the NavController already
    // created and having its state restored by that point.
    super.onCreate(savedInstanceState)
}

4.4 NavHostFragment.onCreateNavController()

@CallSuper
protected open fun onCreateNavHostController(navHostController: NavHostController) {
    onCreateNavController(navHostController)
}
@Suppress("DEPRECATION")
@CallSuper
@Deprecated(
    """Override {@link #onCreateNavHostController(NavHostController)} to gain
    access to the full {@link NavHostController} that is created by this NavHostFragment."""
)
protected open fun onCreateNavController(navController: NavController) {
    //通过NavigatorProvider添加Navigator
    navController.navigatorProvider +=
    DialogFragmentNavigator(requireContext(), childFragmentManager)
    navController.navigatorProvider.addNavigator(createFragmentNavigator())
}

4.5 NavController.setGraph()

@MainThread
@CallSuper
public open fun setGraph(@NavigationRes graphResId: Int) {
    setGraph(navInflater.inflate(graphResId), null)
}

调用NavInflater.inflate()去解析xml,获取导航目的地:

@SuppressLint("ResourceType")
public fun inflate(@NavigationRes graphResId: Int): NavGraph {
    val res = context.resources
    val parser = res.getXml(graphResId)
    val attrs = Xml.asAttributeSet(parser)
    return try {
        var type: Int
        while (parser.next().also { type = it } != XmlPullParser.START_TAG &&
                type != XmlPullParser.END_DOCUMENT
              ) { /* Empty loop */
        }
        if (type != XmlPullParser.START_TAG) {
            throw XmlPullParserException("No start tag found")
        }
        val rootElement = parser.name
        //解析导航目的地
        val destination = inflate(res, parser, attrs, graphResId)
        require(destination is NavGraph) {
            "Root element <$rootElement> did not inflate into a NavGraph"
        }
        destination
    } catch (e: Exception) {
        throw RuntimeException(
            "Exception inflating ${res.getResourceName(graphResId)} line ${parser.lineNumber}",
            e
        )
    } finally {
        parser.close()
    }
}

然后继续往下走:

@MainThread
@CallSuper
public open fun setGraph(graph: NavGraph, startDestinationArgs: Bundle?) {
    if (_graph != graph) {
        _graph?.let { previousGraph ->
            // Clear all saved back stacks by iterating through a copy of the saved keys,
            // thus avoiding any concurrent modification exceptions
            val savedBackStackIds = ArrayList(backStackMap.keys)
            savedBackStackIds.forEach { id ->
                clearBackStackInternal(id)
            }
            // Pop everything from the old graph off the back stack
            //把旧的导航图从栈里面弹出来
            popBackStackInternal(previousGraph.id, true)
        }
        _graph = graph
        onGraphCreated(startDestinationArgs)
    } else {
        for (i in 0 until graph.nodes.size()) {
            val newDestination = graph.nodes.valueAt(i)
            _graph!!.nodes.replace(i, newDestination)
            backQueue.filter { currentEntry ->
                currentEntry.destination.id == newDestination?.id
            }.forEach { entry ->
                entry.destination = newDestination
            }
        }
    }
}

4.6 NavController.onGraphCreated()

@MainThread
private fun onGraphCreated(startDestinationArgs: Bundle?) {
    navigatorStateToRestore?.let { navigatorStateToRestore ->
        val navigatorNames = navigatorStateToRestore.getStringArrayList(
            KEY_NAVIGATOR_STATE_NAMES
        )
        if (navigatorNames != null) {
            for (name in navigatorNames) {
                val navigator = _navigatorProvider.getNavigator<Navigator<*>>(name)
                val bundle = navigatorStateToRestore.getBundle(name)
                if (bundle != null) {
                    navigator.onRestoreState(bundle)
                }
            }
        }
    }
    backStackToRestore?.let { backStackToRestore ->
        for (parcelable in backStackToRestore) {
            val state = parcelable as NavBackStackEntryState
            val node = findDestination(state.destinationId)
            if (node == null) {
                val dest = NavDestination.getDisplayName(
                    context,
                    state.destinationId
                )
                throw IllegalStateException(
                    "Restoring the Navigation back stack failed: destination $dest cannot be " +
                        "found from the current destination $currentDestination"
                )
            }
            val entry = state.instantiate(context, node, hostLifecycleState, viewModel)
            val navigator = _navigatorProvider.getNavigator<Navigator<*>>(node.navigatorName)
            val navigatorBackStack = navigatorState.getOrPut(navigator) {
                NavControllerNavigatorState(navigator)
            }
            backQueue.add(entry)
            navigatorBackStack.addInternal(entry)
            val parent = entry.destination.parent
            if (parent != null) {
                linkChildToParent(entry, getBackStackEntry(parent.id))
            }
        }
        updateOnBackPressedCallbackEnabled()
        this.backStackToRestore = null
    }
    // Mark all Navigators as attached
    _navigatorProvider.navigators.values.filterNot { it.isAttached }.forEach { navigator ->
        val navigatorBackStack = navigatorState.getOrPut(navigator) {
            NavControllerNavigatorState(navigator)
        }
        navigator.onAttach(navigatorBackStack)
    }
    if (_graph != null && backQueue.isEmpty()) {
        val deepLinked =
        !deepLinkHandled && activity != null && handleDeepLink(activity!!.intent)
        if (!deepLinked) {
            // Navigate to the first destination in the graph
            // if we haven't deep linked to a destination
            //导航到图中的第一个目的地
            navigate(_graph!!, startDestinationArgs, null, null)
        }
    } else {
        dispatchOnDestinationChanged()
    }
}

4.7 NavController.navigate()

@MainThread
private fun navigate(
    node: NavDestination,
    args: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
        ) {
    navigatorState.values.forEach { state ->
        state.isNavigating = true
    }
    var popped = false
    var launchSingleTop = false
    var navigated = false
    if (navOptions != null) {
        if (navOptions.popUpToId != -1) {
            popped = popBackStackInternal(
                navOptions.popUpToId,
                navOptions.isPopUpToInclusive(),
                navOptions.shouldPopUpToSaveState()
            )
        }
    }
    val finalArgs = node.addInDefaultArgs(args)
    // Now determine what new destinations we need to add to the back stack
    if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
        navigated = restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras)
    } else {
        val currentBackStackEntry = currentBackStackEntry
        //获取到Navigator
        val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
            node.navigatorName
        )
        if (navOptions?.shouldLaunchSingleTop() == true &&
                node.id == currentBackStackEntry?.destination?.id
           ) {
            unlinkChildFromParent(backQueue.removeLast())
            val newEntry = NavBackStackEntry(currentBackStackEntry, finalArgs)
            backQueue.addLast(newEntry)
            val parent = newEntry.destination.parent
            if (parent != null) {
                linkChildToParent(newEntry, getBackStackEntry(parent.id))
            }
            //调用Navigator的onLaunchSingleTop方法
            //以SingleTop的方式启动,支持导航到Activity
            navigator.onLaunchSingleTop(newEntry)
            launchSingleTop = true
        } else {
            // Not a single top operation, so we're looking to add the node to the back stack
            val backStackEntry = NavBackStackEntry.create(
                context, node, finalArgs, hostLifecycleState, viewModel
            )
            //调用NavController的navigateInternal方法
            //以正常方式导航
            navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
                navigated = true
                addEntryToBackStack(node, finalArgs, it)
            }
        }
    }
    updateOnBackPressedCallbackEnabled()
    navigatorState.values.forEach { state ->
        state.isNavigating = false
    }
    if (popped || navigated || launchSingleTop) {
        dispatchOnDestinationChanged()
    } else {
        updateBackStackLifecycle()
    }
}

4.8 Navigator.onLaunchSingleTop()

@Suppress("UNCHECKED_CAST")
public open fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
    val destination = backStackEntry.destination as? D ?: return
    //调用了Navigator的navigate方法,该方法实现类在ActivityNavigator中
    navigate(destination, null, navOptions { launchSingleTop = true }, null)
    state.onLaunchSingleTop(backStackEntry)
}

调用Navigator的4参navigate方法:

@Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType")
public open fun navigate(
    destination: D,
    args: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Extras?
    ): NavDestination? = destination

navigate有两处实现,一个在ActivityNavigator,一个在NoOpNavigator

看下ActivityNavigator中的实现,主要功能就是设置参数、动画之后跳转到Activity

override fun navigate(
    destination: Destination,
    args: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
        ): NavDestination? {
    checkNotNull(destination.intent) {
        ("Destination ${destination.id} does not have an Intent set.")
    }
    val intent = Intent(destination.intent)
    if (args != null) {
        intent.putExtras(args)
        val dataPattern = destination.dataPattern
        if (!dataPattern.isNullOrEmpty()) {
            // Fill in the data pattern with the args to build a valid URI
            val data = StringBuffer()
            val fillInPattern = Pattern.compile("\{(.+?)\}")
            val matcher = fillInPattern.matcher(dataPattern)
            while (matcher.find()) {
                val argName = matcher.group(1)
                if (args.containsKey(argName)) {
                    matcher.appendReplacement(data, "")
                    data.append(Uri.encode(args[argName].toString()))
                } else {
                    throw IllegalArgumentException(
                        "Could not find $argName in $args to fill data pattern $dataPattern"
                    )
                }
            }
            matcher.appendTail(data)
            intent.data = Uri.parse(data.toString())
        }
    }
    if (navigatorExtras is Extras) {
        intent.addFlags(navigatorExtras.flags)
    }
    if (hostActivity == null) {
        // If we're not launching from an Activity context we have to launch in a new task.
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    }
    if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
    }
    if (hostActivity != null) {
        val hostIntent = hostActivity.intent
        if (hostIntent != null) {
            val hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0)
            if (hostCurrentId != 0) {
                intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId)
            }
        }
    }
    val destId = destination.id
    intent.putExtra(EXTRA_NAV_CURRENT, destId)
    val resources = context.resources
    if (navOptions != null) {
        val popEnterAnim = navOptions.popEnterAnim
        val popExitAnim = navOptions.popExitAnim
        if (
            popEnterAnim > 0 && resources.getResourceTypeName(popEnterAnim) == "animator" ||
                popExitAnim > 0 && resources.getResourceTypeName(popExitAnim) == "animator"
        ) {
            Log.w(
                LOG_TAG,
                "Activity destinations do not support Animator resource. Ignoring " +
                    "popEnter resource ${resources.getResourceName(popEnterAnim)} and " +
                    "popExit resource ${resources.getResourceName(popExitAnim)} when " +
                    "launching $destination"
            )
        } else {
            // For use in applyPopAnimationsToPendingTransition()
            intent.putExtra(EXTRA_POP_ENTER_ANIM, popEnterAnim)
            intent.putExtra(EXTRA_POP_EXIT_ANIM, popExitAnim)
        }
    }
    if (navigatorExtras is Extras) {
        val activityOptions = navigatorExtras.activityOptions
        if (activityOptions != null) {
            ActivityCompat.startActivity(context, intent, activityOptions.toBundle())
        } else {
            context.startActivity(intent)
        }
    } else {
        context.startActivity(intent)
    }
    if (navOptions != null && hostActivity != null) {
        var enterAnim = navOptions.enterAnim
        var exitAnim = navOptions.exitAnim
        if (
            enterAnim > 0 && (resources.getResourceTypeName(enterAnim) == "animator") ||
                exitAnim > 0 && (resources.getResourceTypeName(exitAnim) == "animator")
        ) {
            Log.w(
                LOG_TAG,
                "Activity destinations do not support Animator resource. " +
                    "Ignoring " + "enter resource " + resources.getResourceName(enterAnim) +
                    " and exit resource " + resources.getResourceName(exitAnim) + "when " +
                    "launching " + destination
            )
        } else if (enterAnim >= 0 || exitAnim >= 0) {
            enterAnim = enterAnim.coerceAtLeast(0)
            exitAnim = exitAnim.coerceAtLeast(0)
            hostActivity.overridePendingTransition(enterAnim, exitAnim)
        }
    }
    
    // You can't pop the back stack from the caller of a new Activity,
    // so we don't add this navigator to the controller's back stack
    return null
    }

4.9 NavController.navigateInternal()

private fun Navigator<out NavDestination>.navigateInternal(
    entries: List<NavBackStackEntry>,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?,
    handler: (backStackEntry: NavBackStackEntry) -> Unit = {}
) {
    addToBackStackHandler = handler
    //导航
    navigate(entries, navOptions, navigatorExtras)
    addToBackStackHandler = null
}

调用了Navigator的3参navigate方法:

@Suppress("UNCHECKED_CAST")
public open fun navigate(
    entries: List<NavBackStackEntry>,
    navOptions: NavOptions?,
    navigatorExtras: Extras?
        ) {
    entries.asSequence().map { backStackEntry ->
        val destination = backStackEntry.destination as? D ?: return@map null
        //调用Navigator的naviagte
        val navigatedToDestination = navigate(
            destination, backStackEntry.arguments, navOptions, navigatorExtras
        )
        when (navigatedToDestination) {
            null -> null
            destination -> backStackEntry
            else -> {
                state.createBackStackEntry(
                    navigatedToDestination,
                    navigatedToDestination.addInDefaultArgs(backStackEntry.arguments)
                )
            }
        }
    }.filterNotNull().forEach { backStackEntry ->
        state.push(backStackEntry)
    }
}

接着调用Navigator的4参navigate方法:

@Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType")
public open fun navigate(
    destination: D,
    args: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Extras?
): NavDestination? = destination

4.10 FragmentNavigator.navigate()

Navigator.navigate()具体实现在FragmentNavigator中,通过反射获取到目标Fragment的实例,然后设置动画和参数:

private fun navigate(
    entry: NavBackStackEntry,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
        ) {
    val backStack = state.backStack.value
    val initialNavigation = backStack.isEmpty()
    val restoreState = (
        navOptions != null && !initialNavigation &&
            navOptions.shouldRestoreState() &&
            savedIds.remove(entry.id)
    )
    if (restoreState) {
        // Restore back stack does all the work to restore the entry
        fragmentManager.restoreBackStack(entry.id)
        state.push(entry)
        return
    }
    val destination = entry.destination as Destination
    val args = entry.arguments
    var className = destination.className
    if (className[0] == '.') {
        className = context.packageName + className
    }
    //反射获取到Fragment实例
    val frag = fragmentManager.fragmentFactory.instantiate(context.classLoader, className)
    frag.arguments = args
    val ft = fragmentManager.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)
    }
    ft.replace(containerId, frag)
    ft.setPrimaryNavigationFragment(frag)
    @IdRes val destId = destination.id
    // TODO Build first class singleTop behavior for fragments
    val isSingleTopReplacement = (
        navOptions != null && !initialNavigation &&
            navOptions.shouldLaunchSingleTop() &&
            backStack.last().destination.id == destId
    )
    val isAdded = when {
        initialNavigation -> {
            true
        }
        isSingleTopReplacement -> {
            // Single Top means we only want one instance on the back stack
            if (backStack.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
                fragmentManager.popBackStack(
                    entry.id,
                    FragmentManager.POP_BACK_STACK_INCLUSIVE
                )
                ft.addToBackStack(entry.id)
            }
            false
        }
        else -> {
            ft.addToBackStack(entry.id)
            true
        }
    }
    if (navigatorExtras is Extras) {
        for ((key, value) in navigatorExtras.sharedElements) {
            ft.addSharedElement(key, value)
        }
    }
    ft.setReorderingAllowed(true)
    ft.commit()
    // The commit succeeded, update our view of the world
    if (isAdded) {
        state.push(entry)
    }
}

4.11 FragmentFactory.instantiate()

instantiate方法中,通过反射拿到Fragment实例:

@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
    try {
        Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
        return cls.getConstructor().newInstance();
    } catch (java.lang.InstantiationException e) {
        ...
    }
}
@NonNull
public static Class<? extends Fragment> loadFragmentClass(@NonNull ClassLoader classLoader,
                                                          @NonNull String className) {
    try {
        Class<?> clazz = loadClass(classLoader, className);
        return (Class<? extends Fragment>) clazz;
    } catch (ClassNotFoundException e) {
        ...
        }
}
private static Class<?> loadClass(@NonNull ClassLoader classLoader,
                                  @NonNull String className) throws ClassNotFoundException {
    SimpleArrayMap<String, Class<?>> classMap = sClassCacheMap.get(classLoader);
    if (classMap == null) {
        classMap = new SimpleArrayMap<>();
        sClassCacheMap.put(classLoader, classMap);
    }
    Class<?> clazz = classMap.get(className);
    if (clazz == null) {
        // Class not found in the cache, see if it's real, and try to add it
        clazz = Class.forName(className, false, classLoader);
        classMap.put(className, clazz);
    }
    return clazz;
}

完整示例代码:https://gitee.com/mushuicode/navigation-sample

关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容