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

1 簡介

Navigation是Android Jetpack中的一個框架诽表,用于在Android應(yīng)用中的“目標”之間導(dǎo)航唉锌,該框架提供一致的 API,“目標”可以是Fragment竿奏、Activity或者其他組件袄简。

導(dǎo)航組件由以下三個關(guān)鍵部分組成:

  • 導(dǎo)航圖: 在一個集中位置包含所有導(dǎo)航相關(guān)信息的XML資源。這包括應(yīng)用內(nèi)所有單個內(nèi)容區(qū)域(稱為目標)以及用戶可以通過應(yīng)用獲取的可能路徑泛啸。
  • NavHost:顯示導(dǎo)航圖中目標的空白容器绿语。導(dǎo)航組件包含一個默認NavHost實現(xiàn) (NavHostFragment),可顯示Fragment目標候址。
  • NavController:NavHost中管理應(yīng)用導(dǎo)航的對象吕粹。當(dāng)用戶在整個應(yīng)用中移動時,NavController會安排NavHost中目標內(nèi)容的交換岗仑。

導(dǎo)航組件提供各種其他優(yōu)勢匹耕,包括以下內(nèi)容:

  • 處理Fragment事務(wù)。
  • 默認情況下荠雕,正確處理往返操作泌神。
  • 為動畫和轉(zhuǎn)換提供標準化資源。
  • 實現(xiàn)和處理深層鏈接舞虱。
  • 包括導(dǎo)航界面模式(例如抽屜式導(dǎo)航欄和底部導(dǎo)航),用戶只需完成極少的額外工作母市。
  • Safe Args - 可在目標之間導(dǎo)航和傳遞數(shù)據(jù)時提供類型安全的Gradle插件矾兜。
  • ViewModel支持 - 您可以將ViewModel的范圍限定為導(dǎo)航圖,以在圖表的目標之間共享與界面相關(guān)的數(shù)據(jù)患久。
  • 可以使用Android Studio的Navigation Editor來查看和編輯導(dǎo)航圖椅寺。

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

2 示例代碼

以下示例代碼是在Android Studio創(chuàng)建的模板代碼Bottom Navigation Activity下修改的。

UI就是下圖的樣子蒋失,很簡單返帕,1個Activity,3個Fragment篙挽。點擊HomeFragment中的按鈕荆萤,能跳轉(zhuǎn)到一個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作為主導(dǎo)航,NavHostFragment里能獲取到NavController來管理控制其他Fragment敞峭。

fragment標簽有一個屬性navGraph踊谋,navGraph是導(dǎo)航圖,導(dǎo)航圖文件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個Fragment殖蚕,HomeFragment下有一個action標簽,表示跳轉(zhuǎn)沉迹,action標簽下的destination屬性表示要跳轉(zhuǎn)到的位置睦疫,這里是跳到DetailFragment

點擊XML頁面右上角的Design胚股,可以切換到導(dǎo)航編輯界面笼痛,可以用鼠標按鈕進行跳轉(zhuǎn)、主Fragment修改琅拌,以及添加新的Fragment缨伊。

在activity_main.xml中的BottomNavigationView標簽下,menu屬性引入了菜單文件bottom_nav_menu.xml进宝,該文件位于res/menu下刻坊,每個item的id和mobile_navigation.xml中每個fragment的id一一對應(yīng):

<?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關(guān)聯(lián)起來即可:

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中党晋,點擊按鈕可以跳轉(zhuǎn)到DetailFragment谭胚,調(diào)用的是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
        }

        //跳轉(zhuǎn)到詳情頁
        _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流程圖

這是分析完源碼之后畫出的流程圖灾而,可以先看一遍流程圖,然后再看下面的源碼分析扳剿,看完源碼之后再結(jié)合流程圖加深印象旁趟。

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

4 源碼分析

以下以navigation 2.4.2進行源碼分析,navigation的源碼稍微有點復(fù)雜庇绽,需要花費一定時間去讀锡搜。

4.1 NavHostFragment.create()

NavHostFragment實例化的時候,會調(diào)用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) {
            //設(shè)置參數(shù)
            result.arguments = b
        }
        return result
    }
}

4.2 NavHostFragment.onInflate()

由于NavHostFragment是通過fragment標簽引入,是一個靜態(tài)Fragment辟狈,靜態(tài)FragmentonInflate中初始化肠缔,所以NavHostFragment會先執(zhí)行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設(shè)為默認顯示的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()
        //設(shè)置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)
}

調(diào)用NavInflater.inflate()去解析xml亚隅,獲取導(dǎo)航目的地:

@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
        //解析導(dǎo)航目的地
        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()
    }
}

然后繼續(xù)往下走:

@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
            //把舊的導(dǎo)航圖從棧里面彈出來
            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
            //導(dǎo)航到圖中的第一個目的地
            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))
            }
            //調(diào)用Navigator的onLaunchSingleTop方法
            //以SingleTop的方式啟動硼莽,支持導(dǎo)航到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
            )
            //調(diào)用NavController的navigateInternal方法
            //以正常方式導(dǎo)航
            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
    //調(diào)用了Navigator的navigate方法,該方法實現(xiàn)類在ActivityNavigator中
    navigate(destination, null, navOptions { launchSingleTop = true }, null)
    state.onLaunchSingleTop(backStackEntry)
}

調(diào)用Navigator的4參navigate方法:

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

navigate有兩處實現(xiàn)煮纵,一個在ActivityNavigator懂鸵,一個在NoOpNavigator

看下ActivityNavigator中的實現(xiàn)行疏,主要功能就是設(shè)置參數(shù)匆光、動畫之后跳轉(zhuǎn)到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
    //導(dǎo)航
    navigate(entries, navOptions, navigatorExtras)
    addToBackStackHandler = null
}

調(diào)用了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
        //調(diào)用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)
    }
}

接著調(diào)用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()具體實現(xiàn)在FragmentNavigator中,通過反射獲取到目標Fragment的實例酿联,然后設(shè)置動畫和參數(shù):

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

關(guān)注木水小站 (zhangmushui.cn)和微信公眾號【木水Code】,及時獲取更多最新技術(shù)干貨贞让。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末周崭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喳张,更是在濱河造成了極大的恐慌续镇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件销部,死亡現(xiàn)場離奇詭異摸航,居然都是意外死亡,警方通過查閱死者的電腦和手機舅桩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門酱虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擂涛,你說我怎么就攤上這事逢净。” “怎么了歼指?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甥雕。 經(jīng)常有香客問我踩身,道長,這世上最難降的妖魔是什么社露? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任挟阻,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘附鸽。我一直安慰自己脱拼,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布坷备。 她就那樣靜靜地躺著熄浓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪省撑。 梳的紋絲不亂的頭發(fā)上赌蔑,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音竟秫,去河邊找鬼娃惯。 笑死,一個胖子當(dāng)著我的面吹牛肥败,可吹牛的內(nèi)容都是我干的趾浅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼馒稍,長吁一口氣:“原來是場噩夢啊……” “哼皿哨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筷黔,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤往史,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佛舱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椎例,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年请祖,在試婚紗的時候發(fā)現(xiàn)自己被綠了订歪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肆捕,死狀恐怖刷晋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慎陵,我是刑警寧澤眼虱,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布捏悬,位于F島的核電站刀疙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亏娜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一垃沦、第九天 我趴在偏房一處隱蔽的房頂上張望桩引。 院中可真熱鬧,春花似錦忱详、人聲如沸航唆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吴趴。三九已至陨闹,卻和暖如春寨闹,著一層夾襖步出監(jiān)牢的瞬間胶坠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工繁堡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沈善,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓椭蹄,卻偏偏與公主長得像闻牡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绳矩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容