jetpack之Navigation的源码解读

源码版本:Android 10

一、Navigation基本使用方法

1.1 创建navigation使用所需资源文件

jetpack之Navigation的源码解读_第1张图片

nav_graph.xml源码如下

<?xml version="1.0" encoding="utf-8"?>
<!--
    android:id="@+id/nav_graph",android:id可以是任意的
-->
<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/nav_graph"
    app:startDestination="@id/mainFragment"
    tools:ignore="UnusedNavigation">
    <!--
        app:startDestination值是要开启的页面
        android:name值要开启页面的全类名
        android:label要开启页面的标签
        tools:layout用作预览
        app:enterAnim页面进入的动画
        app:exitAnim页面退出的动画

        app:popUpTo 会告知 Navigation 库在调用 navigate() 的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。

        如果存在A→B→C→A,
        在到达目的地 C 之后,返回堆栈包含每个目的地(A、B、C)的实例。当返回到目的地 A 时,我们也 popUpTo A,
        也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用 app:popUpToInclusive="true",我们还会将第一个
         A 从堆栈上弹出,从而有效地清除它。请注意,如果您不使用 app:popUpToInclusive,则返回堆栈会包含两个目的地 A 的实例。

    -->
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.study.jetpackstudykotlin.navigation.main.MainFragment"
        android:label="MainFragment"
        tools:layout="@layout/main_fragment">
        <!--
            app:popUpToInclusive="true"开启DetailFragment时,将会弹出MainFragment
          -->
        <action
            android:id="@+id/action_mainFragment_to_detailFragment"
            app:destination="@id/detailFragment"
            app:enterAnim="@anim/fragment_close_enter"
            app:exitAnim="@anim/fragment_close_exit"
            app:launchSingleTop="true"
            app:popEnterAnim="@anim/fragment_fade_enter"
            app:popExitAnim="@anim/fragment_fade_exit"
            app:popUpTo="@id/mainFragment"
            app:popUpToInclusive="true" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.study.jetpackstudykotlin.navigation.detail.DetailFragment"
        android:label="DetailFragment"
        tools:layout="@layout/detail_fragment">
        <action
            android:id="@+id/action_detailFragment_to_webViewActivity"
            app:destination="@id/webViewDetail"
            app:enterAnim="@anim/fragment_close_enter"
            app:exitAnim="@anim/fragment_close_exit"
            app:popEnterAnim="@anim/fragment_open_enter"
            app:popExitAnim="@anim/fragment_open_exit" />
        <deepLink app:uri="inApp://jump" />
    </fragment>


    <activity
        android:id="@+id/webViewDetail"
        android:name="com.study.jetpackstudykotlin.navigation.WebViewActivity"
        android:label="WebViewActivity"
        tools:layout="@layout/activity_web_view" />
</navigation>

1.2 NavigationMainActivity源码

package com.study.jetpackstudykotlin.navigation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
import com.study.jetpackstudykotlin.R

class NavigationMainActivity : AppCompatActivity() {

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_navigation_main)
        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)
         //在此使用时只有Fragment切换时才会展示默认的返回按钮
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeButtonEnabled(true)
		//导航控制器
        navController = findNavController(R.id.fragment)
        setupActionBarWithNavController(navController, null)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        /*
        * MainFragment→DetailFragment使用了app:popUpToInclusive="true"
        *
        * 当调用navigateUp方法时会重新开启MainFragment,而直接点击系统返回按钮
        * 就不会存在该问题
        * */
        if (item.itemId == android.R.id.home) {
            navController.navigateUp()
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}

activity_navigation_main.xml源码如下所示:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".navigation.NavigationMainActivity">


    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#86B951"
        android:fitsSystemWindows="true"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="@string/app_name" />



    <fragment
        android:id="@+id/fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?attr/actionBarSize"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar"
        app:navGraph="@navigation/nav_graph" />


</androidx.constraintlayout.widget.ConstraintLayout>

1.3 MainFragment源码

package com.study.jetpackstudykotlin.navigation.main

import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController

import com.study.jetpackstudykotlin.R
import kotlinx.android.synthetic.main.main_fragment.*

class MainFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btn.setOnClickListener {
            //导航至DetailFragment
            findNavController().navigate(R.id.action_mainFragment_to_detailFragment)
        }
        //隐式链接
        btn_detail.setOnClickListener {
            val deeplink = Uri.parse("inApp://jump")
            findNavController().navigate(deeplink)
        }
    }

}

使用隐式链接跳转时,AndroidMainfest.xml源码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.study.jetpackstudykotlin">
    <!-- To access Google+ APIs: -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".navigation.WebViewActivity"></activity>
        <activity android:name=".navigation.NavigationMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            //隐式链接,必须添加。编译时将会将其转换为intent-filter
            <nav-graph android:value="@navigation/nav_graph" />
        </activity>
    
    </application>

</manifest>

1.4 DetailFragment源码

package com.study.jetpackstudykotlin.navigation.detail

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController

import com.study.jetpackstudykotlin.R
import kotlinx.android.synthetic.main.detail_fragment.*

class DetailFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.detail_fragment, container, false)
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        tv_detail.setOnClickListener {
        //导航至WebViewActivity
            findNavController().navigate(R.id.action_detailFragment_to_webViewActivity)
        }
    }

}

二、Navigation源码分析

    //由父级调用,xml文件中NavHostFragment的入口
    @CallSuper
    @Override
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        super.onInflate(context, attrs, savedInstanceState);

        final TypedArray navHost = context.obtainStyledAttributes(attrs,
                androidx.navigation.R.styleable.NavHost);
        //获取app:navGraph的值      
        final int graphId = navHost.getResourceId(
                androidx.navigation.R.styleable.NavHost_navGraph, 0);
        if (graphId != 0) {
            mGraphId = graphId;
        }
        navHost.recycle();

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
        final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
        //判断当前是否是主NavHost
        if (defaultHost) {
            mDefaultNavHost = true;
        }
        a.recycle();
    }

	@CallSuper
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        //为true时,将当前实例添加到父级的Fragment管理器中,同时设置
        //其为主导航的Fragment
        if (mDefaultNavHost) {
            getParentFragmentManager().beginTransaction()
                    .setPrimaryNavigationFragment(this)
                    .commit();
        }
    }

	@CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();
        //创建导航控制器
        mNavController = new NavHostController(context);
        //将Fragment与导航器绑定
        mNavController.setLifecycleOwner(this);
        //赋予mNavController处理用户点击系统返回键的事件
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        //激活返回监听
        mNavController.enableOnBackPressed(
                mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        mNavController.setViewModelStore(getViewModelStore());
        //创建导航器
        onCreateNavController(mNavController);

        Bundle navState = null;
        //是否之前有过缓存
        if (savedInstanceState != null) {
            navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
            if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true;
                getParentFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
            mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
        }

        if (navState != null) {
            // 恢复导航状态
            mNavController.restoreState(navState);
        }
        if (mGraphId != 0) {
            // Set from onInflate()
            mNavController.setGraph(mGraphId);
        } else {
            // 通过Bundle获取graphId
            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.setGraph方法最终调用的是NavController类中的onGraphCreated方法,该方法源码如下所示:

 private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        //....
        if (mGraph != null && mBackStack.isEmpty()) {
            boolean deepLinked = !mDeepLinkHandled && mActivity != null
                    && handleDeepLink(mActivity.getIntent());
            //不存在隐式的调用      
            if (!deepLinked) {
                // 开始导航
                navigate(mGraph, startDestinationArgs, null, null);
            }
        }
}

上面navigate方法的源码如下所示:

private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        //....实现Fragment添加的关键代码
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        //....
    }

Demo中运用的是FragmentNavigator,因此此处只针对FragmentNavigator的navigate方法进行了分析。该方法
源码如下:

 public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();

        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        //动画处理
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }
        //替换fragment
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        //....
    }

三、总结

这里只是针对源码导航的部分进行了分析,希望能起个抛砖引玉的作用吧。

Navigation时序图

你可能感兴趣的:(Android技巧-源码分析)