美文网首页
Navigation学习【二】——源码简单剖析

Navigation学习【二】——源码简单剖析

作者: 12313凯皇 | 来源:发表于2020-02-02 21:37 被阅读0次

最近在学习Jetpack全组件实战,记录一下其中相关知识点。

Navigation工作流程

NavHostFragment开始,它是一个内容承载容器,从它的onCreate()方法开始看起:

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

    //A-1 -- NavHostController页面导航,页面跳转核心类  一个入口  
    //在这里将内容切换的能力委托给了NavHostController
    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());
    //A-2 -- onCreateNavController
    onCreateNavController(mNavController);

    ...
}

@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
    //3.DialogFragmentNavigator
    navController.getNavigatorProvider().addNavigator(
            new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
    //4.FragmentNavigator
    navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
    return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
}
//NavHostController.java
public NavHostController(@NonNull Context context) {
    super(context);
}

//NavController.java
public NavController(@NonNull Context context) {
    mContext = context;
    while (context instanceof ContextWrapper) {
        if (context instanceof Activity) {
            mActivity = (Activity) context;
            break;
        }
        context = ((ContextWrapper) context).getBaseContext();
    }
    //1.NavGraphNavigator  注意其构造函数中又把NavigatorProvider传递了进去
    mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
    //2.ActivityNavigator
    mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}

NavController

NavHostFragmentonCreate()方法中,将内容切换的能力委托给了NavHostController,先是创建了一个NavHostController,接着调用了 onCreateNavController 方法。其中一共添加了4Navigator(两个方法各添加了2个)进去,Navigator,顾名思义,是导航器的意思,这四个导航器分别有着不同的职责:

  • NavGraphNavigator —— 当页面资源信息被解析完成之后,会通过它跳转到配置的默认启动页,也就是第一个也页面。
  • ActivityNavigator —— 给Activity提供跳转、切换的能力。
  • **DialogFragmentNavigator ** —— 给DialogFragment提供跳转、切换的能力。
  • FragmentNavigator —— 给Fragment提供跳转、切换的能力。

代码中的 mNavigatorProvider 内部其实是通过 HashMap 以键值对的形式存储了一个个导航器(Navigator)。

//NavigatorProvider.java
public class NavigatorProvider {
    
    private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
            new HashMap<>();
    
    public final Navigator<? extends NavDestination> addNavigator(
            @NonNull Navigator<? extends NavDestination> navigator) {
        //去取Navigator的NAME注解
        String name = getNameForNavigator(navigator.getClass());

        return addNavigator(name, navigator);
    }
    
    public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
            @NonNull Navigator<? extends NavDestination> navigator) {
        if (!validateName(name)) {
            throw new IllegalArgumentException("navigator name cannot be an empty string");
        }
        return mNavigators.put(name, navigator);
    }
    
    @NonNull
    static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
        String name = sAnnotationNames.get(navigatorClass);
        if (name == null) {
            Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
            name = annotation != null ? annotation.value() : null;
            if (!validateName(name)) {
                throw new IllegalArgumentException("No @Navigator.Name annotation found for "
                        + navigatorClass.getSimpleName());
            }
            sAnnotationNames.put(navigatorClass, name);
        }
        return name;
    }
    
    ...
}

Navigator

//Navigator.java
//需要传递一个NavDestination类型的泛型 -- 其作用是为了限定一种Navigator只能创建一种Destination节点信息。
//一个Destination可以代表一个个的页面(activity、fragment、dialog等)
public abstract class Navigator<D extends NavDestination> {
    
    /**
     * This annotation should be added to each Navigator subclass to denote the default name used
     * to register the Navigator with a {@link NavigatorProvider}.
     * 应该将此注释添加到每个Navigator子类中,以表示用{@link NavigatorProvider}注册导航器时使用的默认名称。
     *
     * 作用:
     *   1. 用于NavigatorProvider中存储Navigator所需的key值,value为Navigator本身。
     *   2. 用于Destination中的跳转(跳转需是要根据key值去取相应的Destination)
     *
     *
     * @see NavigatorProvider#addNavigator(Navigator)
     * @see NavigatorProvider#getNavigator(Class)
     */
    @Retention(RUNTIME)
    @Target({TYPE})
    @SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
    public @interface Name {
        String value();
    }
    
    // 具体的页面跳转
    public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);
    
    //需不需要拦截系统返回键,来做一些回退栈的操作
    public abstract boolean popBackStack();
    
    //状态保存
    @Nullable
    public Bundle onSaveState() {
        return null;
    }
    
    //状态恢复
    public void onRestoreState(@NonNull Bundle savedState) {
    }
    
    /**
     * Interface indicating that this class should be passed to its respective
     * 
     * {@link Navigator} to enable Navigator specific behavior. 跳转时可以提供一些额外的行为,如过度元素,转场动画等。
     */
    public interface Extras {
    }
}
public class NavDestination {
    public NavDestination(@NonNull String navigatorName) {
        mNavigatorName = navigatorName;
    }
}

ActivityNavigator

//ActivityNavigator.java
@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination> {
    
    @NonNull
    @Override
    public Destination createDestination() {
        //一个静态内部类
        return new Destination(this);
    }
    
    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (destination.getIntent() == null) {
            throw new IllegalStateException("Destination " + destination.getId()
                    + " does not have an Intent set.");
        }
        Intent intent = new Intent(destination.getIntent());
        ...
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            ActivityOptionsCompat activityOptions = extras.getActivityOptions();
            //启动Activity
            if (activityOptions != null) {
                ActivityCompat.startActivity(mContext, intent, activityOptions.toBundle());
            } else {
                mContext.startActivity(intent);
            }
        } else {
            mContext.startActivity(intent);
        }
        
        ...

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

DialogFragmentNavigator

DeepLink源码简析

先看NavController.handleDeepLink(new Intent())方法:

//NavController.java
public boolean handleDeepLink(@Nullable Intent intent) {
    if (intent == null) {
        return false;
    }
    Bundle extras = intent.getExtras();
    int[] deepLink = extras != null ? extras.getIntArray(KEY_DEEP_LINK_IDS) : null;
    Bundle bundle = new Bundle();
    Bundle deepLinkExtras = extras != null ? extras.getBundle(KEY_DEEP_LINK_EXTRAS) : null;
    if (deepLinkExtras != null) {
        bundle.putAll(deepLinkExtras);
    }
    //intent.getData() 即为所传递过来的uri
    if ((deepLink == null || deepLink.length == 0) && intent.getData() != null) {
        NavDestination.DeepLinkMatch matchingDeepLink = mGraph.matchDeepLink(intent.getData());
        if (matchingDeepLink != null) {
            //buildDeepLinkIds() --> 构建DeepLinkIds,包含了从根节点到当前节点的id数组
            deepLink = matchingDeepLink.getDestination().buildDeepLinkIds();
            bundle.putAll(matchingDeepLink.getMatchingArgs());
        }
    }
    
    ...
        
    if ((flags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
        // Start with a cleared task starting at our root when we're on our own task
        if (!mBackStack.isEmpty()) {
            popBackStackInternal(mGraph.getId(), true);
        }
        int index = 0;
        //遍历deepLinkIds数组
        while (index < deepLink.length) {
            int destinationId = deepLink[index++];
            //每一个NavDestination代表着一个页面
            NavDestination node = findDestination(destinationId);
            if (node == null) {
                throw new IllegalStateException("unknown destination during deep link: "
                                                + NavDestination.getDisplayName(mContext, destinationId));
            }
            //通过navigate方法将所涉及的页面一个一个打开
            navigate(node, bundle,
                     new NavOptions.Builder().setEnterAnim(0).setExitAnim(0).build(), null);
        }
        return true;
    }
    
    ...
}
//NavDestination.java
/**
 * Build an array containing the hierarchy from the root down to this destination.
 * 构建一个包含从根到此目的地的层次结构的数组。
 * @return An array containing all of the ids from the root to this destination
 */
@NonNull
int[] buildDeepLinkIds() {
    ArrayDeque<NavDestination> hierarchy = new ArrayDeque<>();
    NavDestination current = this;
    do {
        NavGraph parent = current.getParent();
        if (parent == null || parent.getStartDestination() != current.getId()) {
            hierarchy.addFirst(current);
        }
        current = parent;
    } while (current != null);
    int[] deepLinkIds = new int[hierarchy.size()];
    int index = 0;
    for (NavDestination destination : hierarchy) {
        deepLinkIds[index++] = destination.getId();
    }
    return deepLinkIds;
}

举例

handleDeepLink.png

如果外部应用打开客户端并携带了uri: ppjoke://page/pageD ,那么 A、B、C、D都会被打开 。[图片上传中...(handleDeepLink.png-45632e-1580650536653-0)]

相关文章

网友评论

      本文标题:Navigation学习【二】——源码简单剖析

      本文链接:https://www.haomeiwen.com/subject/syccxhtx.html