欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android的Jetpack的navigation原理详解

程序员文章站 2022-06-15 22:13:20
还没有用过navigation的小伙伴赶紧来用它,实在是太...

还没有用过navigation的小伙伴赶紧来用它,实在是太方便了,·再也不用写supportFragmentManager.beginTransaction().add()和supportFragmentManager.beginTransaction().replace()来切换布局了,只要将所有的需要跳转的Frament全部放到布局里面就好了,navigation和flutter的路由差不多,它只不过是将frament装到了作为一个个的跳转点而已。我觉着它最大的优势是可以通过android studio清晰的看到每个界面的跳转点。如图所示:

android的Jetpack的navigation原理详解

不用运行就可以看见每个模块的界面跳转,是不是很炫。大家可以参考一下官方文档android导航

用起来也比较简单首先在build.gradle中加入

 implementation "androidx.navigation:navigation-fragment-ktx:2.1.0"
    implementation "androidx.navigation:navigation-ui-ktx:2.1.0"

引入支持库之后,第二步在你的模块主布局中加入需要动态变化的Frament

    <fragment
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/mobile_navigation" />

对,就是上面这个东西,注意一点最终实例化的Fragmentandroidx.navigation.fragment.NavHostFragment类,实例化的Frament是扩展库中的类,注意 app:navGraph="@navigation/mobile_navigation" 这个属性是真正的填充Fragment的类。第三步,在res下新建navigation文件夹,xml内容如下:

<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"
    app:startDestination="@+id/home_dest">
    <fragment
        android:id="@+id/home_dest"
        android:name="com.example.android.codelabs.navigation.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/home_fragment">

        <!-- TODO STEP 7.1 - Add action with transitions -->
        <!--<action-->
            <!--android:id="@+id/next_action"-->
            <!--app:destination="@+id/flow_step_one_dest"-->
            <!--app:enterAnim="@anim/slide_in_right"-->
            <!--app:exitAnim="@anim/slide_out_left"-->
            <!--app:popEnterAnim="@anim/slide_in_left"-->
            <!--app:popExitAnim="@anim/slide_out_right" />-->
        <!-- TODO END STEP 7.1 -->

    </fragment>

    <fragment
        android:id="@+id/flow_step_one_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFragment"
        tools:layout="@layout/flow_step_one_fragment">
        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="1"/>

        <action
            android:id="@+id/next_action"
            app:destination="@+id/flow_step_two_dest">
        </action>
    </fragment>

    <fragment
        android:id="@+id/flow_step_two_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFragment"
        tools:layout="@layout/flow_step_two_fragment">

        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="2"/>

        <action
            android:id="@+id/next_action"
            app:popUpTo="@id/home_dest">
        </action>
    </fragment>

    <!-- TODO STEP 4 Create a new navigation destination pointing to SettingsFragment -->
    <!--<fragment-->
        <!--android:id="@+id/settings_dest"-->
        <!--android:name="com.example.android.codelabs.navigation.SettingsFragment"-->
        <!--android:label="@string/settings"-->
        <!--tools:layout="@layout/settings_fragment" />-->
    <!-- TODO END STEP 4 -->

    <fragment
        android:id="@+id/deeplink_dest"
        android:name="com.example.android.codelabs.navigation.DeepLinkFragment"
        android:label="@string/deeplink"
        tools:layout="@layout/deeplink_fragment">

        <argument
            android:name="myarg"
            android:defaultValue="Android!"/>
        <!-- TODO STEP 11.1 - Add a deep link to www.example.com/{myarg}/ -->

        <!--<deepLink app:uri="www.example.com/{myarg}" />-->

        <!-- TODO END STEP 11.1 -->
    </fragment>
</navigation>

其中app:startDestination="@+id/home_dest"属性用来声明第一个显示的Fragment,argument标签用来声明传递的属性,action标签是用来跳转的,这和activity的action有异曲同工之妙,跳转到新的Fragment的时候调用Navigation.findNavController(view).navigate(),回到上一个Fragment的时候调Navigation.findNavController(view).navigateUp()

方法。好了,这就是Navigation的基本使用了,大家可以在官网下载demo看一下,接下来来探究一下navigation的原理。

前面介绍了navigation使用的入口就是

androidx.navigation.fragment.NavHostFragment这个类

public class NavHostFragment extends Fragment implements NavHost

NavHostFragment 就是一个Fragment并实现了NavHost接口,这个接口只有一个方法

public interface NavHost {

    /**
     * Returns the {@link NavController navigation controller} for this navigation host.
     *
     * @return this host's navigation controller
     */
    @NonNull
    NavController getNavController();
}

在它的onCreateView中,它只是填充了简单的帧布局FrameLayout,它的用处就是最终用来替换布局的

  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        FrameLayout frameLayout = new FrameLayout(inflater.getContext());
        // When added via XML, this has no effect (since this FrameLayout is given the ID
        // automatically), but this ensures that the View exists as part of this Fragment's View
        // hierarchy in cases where the NavHostFragment is added programmatically as is required
        // for child fragment transactions
        frameLayout.setId(getId());
        return frameLayout;
    }

onCreateView执行完以后会执行onViewCreated,看一下

public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!(view instanceof ViewGroup)) {
            throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
        }
        Navigation.setViewNavController(view, mNavController);
        // When added programmatically, we need to set the NavController on the parent - i.e.,
        // the View that has the ID matching this NavHostFragment.
        if (view.getParent() != null) {
            View rootView = (View) view.getParent();
            if (rootView.getId() == getId()) {
                Navigation.setViewNavController(rootView, mNavController);
            }
        }
    }

很简单就是为当前Fragment设置当前的根view设置一个tag,这个tag的值是NavHostController,这个类实现了对Fragment的控制的封装。而onInflate是在fragment标签被解析的时候调用的,它早于fragment的任何生命周期方法

  public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        super.onInflate(context, attrs, savedInstanceState);

        final TypedArray navHost = context.obtainStyledAttributes(attrs, R.styleable.NavHost);
        final int graphId = navHost.getResourceId(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);
        if (defaultHost) {
            mDefaultNavHost = true;
        }
        a.recycle();
    }

这里根据属性解析出app:navGraph引用的xml的id

然后在onCreate方法通过  mNavController.setGraph(mGraphId)方法解析出来第一个需要显示的Fragment

 public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();

        mNavController = new NavHostController(context);
        mNavController.setLifecycleOwner(this);
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        // Set the default state - this will be updated whenever
        // onPrimaryNavigationFragmentChanged() is called
        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;
                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);
            }
        }
    }

继续看

  public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
        if (mGraph != null) {
            // Pop everything from the old graph off the back stack
            popBackStackInternal(mGraph.getId(), true);
        }
        mGraph = graph;
        onGraphCreated(startDestinationArgs);
    }

如果mGraph 不为null就证明栈中已经存放了当前的Fragment,直接让它出栈就好了,接着看onGraphCreated方法

 private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        if (mNavigatorStateToRestore != null) {
            ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
                    KEY_NAVIGATOR_STATE_NAMES);
            if (navigatorNames != null) {
                for (String name : navigatorNames) {
                    Navigator navigator = mNavigatorProvider.getNavigator(name);
                    Bundle bundle = mNavigatorStateToRestore.getBundle(name);
                    if (bundle != null) {
                        navigator.onRestoreState(bundle);
                    }
                }
            }
        }
        if (mBackStackUUIDsToRestore != null) {
            for (int index = 0; index < mBackStackUUIDsToRestore.length; index++) {
                UUID uuid = UUID.fromString(mBackStackUUIDsToRestore[index]);
                int destinationId = mBackStackIdsToRestore[index];
                Bundle args = (Bundle) mBackStackArgsToRestore[index];
                NavDestination node = findDestination(destinationId);
                if (node == null) {
                    throw new IllegalStateException("unknown destination during restore: "
                            + mContext.getResources().getResourceName(destinationId));
                }
                if (args != null) {
                    args.setClassLoader(mContext.getClassLoader());
                }
                mBackStack.add(new NavBackStackEntry(uuid, node, args, mViewModel));
            }
            updateOnBackPressedCallbackEnabled();
            mBackStackUUIDsToRestore = null;
            mBackStackIdsToRestore = null;
            mBackStackArgsToRestore = null;
        }
        if (mGraph != null && mBackStack.isEmpty()) {
            boolean deepLinked = !mDeepLinkHandled && mActivity != null
                    && handleDeepLink(mActivity.getIntent());
            if (!deepLinked) {
                // Navigate to the first destination in the graph
                // if we haven't deep linked to a destination
                navigate(mGraph, startDestinationArgs, null, null);
            }
        }
    }

和流程有关的方法是navigate,继续进入


    private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        boolean popped = false;
        if (navOptions != null) {
            if (navOptions.getPopUpTo() != -1) {
                popped = popBackStackInternal(navOptions.getPopUpTo(),
                        navOptions.isPopUpToInclusive());
            }
        }
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        if (newDest != null) {
            if (!(newDest instanceof FloatingWindow)) {
                // We've successfully navigating to the new destination, which means
                // we should pop any FloatingWindow destination off the back stack
                // before updating the back stack with our new destination
                //noinspection StatementWithEmptyBody
                while (!mBackStack.isEmpty()
                        && mBackStack.peekLast().getDestination() instanceof FloatingWindow
                        && popBackStackInternal(
                                mBackStack.peekLast().getDestination().getId(), true)) {
                    // Keep popping
                }
            }
            // The mGraph should always be on the back stack after you navigate()
            if (mBackStack.isEmpty()) {
                mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel));
            }
            // Now ensure all intermediate NavGraphs are put on the back stack
            // to ensure that global actions work.
            ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
            NavDestination destination = newDest;
            while (destination != null && findDestination(destination.getId()) == null) {
                NavGraph parent = destination.getParent();
                if (parent != null) {
                    hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel));
                }
                destination = parent;
            }
            mBackStack.addAll(hierarchy);
            // And finally, add the new destination with its default args
            NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest,
                    newDest.addInDefaultArgs(finalArgs), mViewModel);
            mBackStack.add(newBackStackEntry);
        }
        updateOnBackPressedCallbackEnabled();
        if (popped || newDest != null) {
            dispatchOnDestinationChanged();
        }
    }

这个方法有点长,大部分代码都是保存当前需要显示的Fragment类的封装类NavDestination ,保存到栈中。

navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);

navigator.navigate是真正实现Fragment切换的,真正实现类是FragmentNavigator

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

        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
        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(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

这个方法最重要的就是这个代码   ft.replace(mContainerId, frag),ft是FragmentTransaction 类,而mContainerId就是NavHostFragment的布局View的id如下:

 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        FrameLayout frameLayout = new FrameLayout(inflater.getContext());
        // When added via XML, this has no effect (since this FrameLayout is given the ID
        // automatically), but this ensures that the View exists as part of this Fragment's View
        // hierarchy in cases where the NavHostFragment is added programmatically as is required
        // for child fragment transactions
        frameLayout.setId(getId());
        return frameLayout;
    }

所以Frament的切换都是通过NavHostFragment的FrameLayout 来切换的

 

 

本文地址:https://blog.csdn.net/xiatiandefeiyu/article/details/107518262