`

带你一步一步的解析ARouter 源码

阅读更多

ARouter 是阿里推出的一款页面路由框架。由于项目中采用了组件化架构进行开发,通过 ARouter 实现了页面的跳转,之前看它的源码时忘了写笔记,因此今天来重新对它的源码进行一次分析。

顺手留下GitHub链接,需要获取相关面试或者面试宝典核心笔记PDF等内容的可以自己去找
https://github.com/xiangjiana/Android-MS

(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
(VX:mm14525201314)

本篇源码解析基于 ARouter 1.2.4

初始化

ARouter 在使用前需要通过调用 Arouter.init方法并传入 Application 进行初始化:

  /**
 * Init, it must be call before used router.
 */
  public static void init(Application application) {
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        hasInit = _ARouter.init(application);
        if (hasInit) {
            _ARouter.afterInit();
        }
        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
     }
  }

这里调用到了 _ARouter.init,这个 _ARouter类才是 ARouter 的核心类:

  protected static synchronized boolean init(Application application) {
    mContext = application;
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;

      // It's not a good idea.
     // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
     //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
   
     }
    return true;
   }

这里实际上调用到了 LogisticsCenter.init

 public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;
    try {
        long startInit = System.currentTimeMillis();
        Set<String> routerMap;
        // 获取存储 ClassName 集合的 routerMap(debug 模式下每次都会拿最新的)
        if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
            logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
            // 根据指定的 packageName 获取 package 下的所有 ClassName
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            if (!routerMap.isEmpty()) {
                    // 存入 SP 缓存
                context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
            }
        } else {
            logger.info(TAG, "Load router map from cache.");
            // release 模式下,已经缓存了 ClassName 列表
            routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
        }
        logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
        startInit = System.currentTimeMillis();
        // 遍历 ClassName
        for (String className : routerMap) {
            if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                // 发现是 Root,加载类构建对象后通过 loadInto 加载进 Warehouse.groupsIndex
                ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
            } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                // 发现是 Interceptor,加载类构建对象后通过 loadInto 加载进 Warehouse.interceptorsIndex
                ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
            } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                // 发现是 ProviderGroup,加载类构建对象后通过 loadInto 加载进 Warehouse.providersIndex
                ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
            }
        }
        // ...
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}
这里主要有如下几步:

1.获取 com.alibaba.android.arouter.routes 下存储 ClassName 的集合 routerMap
2.若为 debug 模式或之前没有解析过 routerMap,则通过 ClassUtils.getFileNameByPackageName 方法对指定 package 下的所有 ClassName 进行解析并存入 SP。
3.若并非 debug 模式,并且之前已经解析过,则直接从 SP 中取出。(debug 每次都需要更新,因为类会随着代码的修改而变动)
4.遍历 routerMap 中的 ClassName

  • 如果是 RouteRoot,则加载类构建对象后通过 loadInto 加载进 Warehouse.groupsIndex
  • 如果是InterceptorGroup,则加载类构建对象后通过loadInto 加载进 Warehouse.interceptorsIndex
  • 如果是 ProviderGroup,则加载类构建对象后通过loadInto 加载进Warehouse.providersIndex`。

解析 ClassName

我们先看看 ClassUtils.getFileNameByPackageName 是如何对指定 package 下的 ClassName 集合进行解析的:

  public static Set<String> getFileNameByPackageName(Context context, final String packageName) {
    final Set<String> classNames = new HashSet<>();
    // 通过 getSourcePaths 方法获取 dex 文件 path 集合
    List<String> paths = getSourcePaths(context);
    // 通过 CountDownLatch 对 path 的遍历处理进行控制
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    // 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理
    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                // 加载 path 对应的 dex 文件
                DexFile dexfile = null;
                try {
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                            // zip 结尾通过 DexFile.loadDex 进行加载
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                            // 否则通过 new DexFile 加载
                        dexfile = new DexFile(path);
                    }
                    // 遍历 dex 中的 Entry
                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                            // 如果是对应的 package 下的类,则添加其 className
                        String className = dexEntries.nextElement();
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }
                    parserCtl.countDown();
                }
            }
        });
    }
    // 所有 path 处理完成后,继续向下走
    parserCtl.await();
    Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
    return classNames;
  }

这里的步骤比较简单,主要是如下的步骤:

1.通过 getSourcePaths 方法获取 dex 文件的 path 集合。
2.创建了一个 CountDownLatch 控制 dex 文件的并行处理,以加快速度。
3.遍历 path 列表,通过 DefaultPoolExecutor 对 path 并行处理。
4.加载 path 对应的 dex 文件,并对其中的 Entry 进行遍历,若发现了对应 package 下的 ClassName,将其加入结果集合。
5.所有 dex 处理完成后,返回结果

关于 getSourcePaths 如何获取到的 dex 集合这里就不纠结了,因为我们的关注点不在这里。

初始化 Warehouse

Warehouse 实际上就是仓库的意思,它存放了 ARouter 自动生成的类(RouteRootInterceptorGroupProviderGroup)的信息。

我们先看看 Warehouse 类究竟是怎样的

  class Warehouse {
    // 保存 RouteGroup 对应的 class 以及 RouteMeta
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // 保存 Provider 以及 RouteMeta
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // 保存 Interceptor 对应的 class 以及 Inteceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
  }

可以发现 Warehouse 就是一个纯粹用来存放信息的仓库类,它的数据的实际上是通过上面的几个自动生成的类在 loadInto 中对 Warehouse 主动填入数据实现的。

例如我们打开一个自动生成的 IRouteRoot 的实现类:

 public class ARouter$$Root$$homework implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("homework", ARouter$$Group$$homework.class);
  }
 }

可以看到,它在 groupsIndex 中对这个 RouteRoot 中的 IRouteGroup 进行了注册,也就是向 groupIndex 中注册了 Route Group 对应的 IRouteGroup 类。其他类也是一样,通过自动生成的代码将数据填入 Map 或 List 中。

可以发现,初始化过程主要完成了对自动生成的路由相关类 RouteRootInterceptorProviderGroup 的加载,对它们通过反射构造后将信息加载进了 Warehouse 类中。

路由跳转

Postcard 的创建

下面我们看看路由的跳转是如何实现的,我们先看到 ARouter.build 方法:

 public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
 }

它转调到了 _ARouter 的 build 方法:

  protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path));
    }
  }

它首先通过 ARouter.navigation 获取到了 PathReplaceService,它需要用户进行实现,若没有实现会返回 null,若有实现则调用了它的 forString 方法传入了用户的 Route Path 进行路径的预处理。

最后转调到了 build(path, group),group 通过 extractGroup 得到:

 private String extractGroup(String path) {
    if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
        throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
    }
    try {
        String defaultGroup = path.substring(1, path.indexOf("/", 1));
        if (TextUtils.isEmpty(defaultGroup)) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
        } else {
            return defaultGroup;
        }
    } catch (Exception e) {
        logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
        return null;
    }
  }

extractGroup 实际上就是对字符串处理,取出 Route Group 的名称部分。

  protected Postcard build(String path, String group) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return new Postcard(path, group);
    }
  }

build(path, group) 方法同样也会尝试获取到 PathReplaceService 并对 path 进行预处理。之后通过 path 与 group 构建了一个 Postcard 类:

  public Postcard(String path, String group) {
       this(path, group, null, null);
  }

  public Postcard(String path, String group, Uri uri, Bundle bundle) {
    setPath(path);
    setGroup(group);
    setUri(uri);
    this.mBundle = (null == bundle ? new Bundle() : bundle);
  }

这里最终调用到了 PostCard(path, group, uri, bundle),这里只是进行了一些参数的设置。

之后,如果我们调用 withIntwithDouble 等方法,就可以进行参数的设置。例如 withInt 方法:

  public Postcard withInt(@Nullable String key, int value) {
    mBundle.putInt(key, value);
    return this;
  }

它实际上就是在对 Bundle 中设置对应的 key、value。

最后我们通过 navigation 即可实现最后的跳转:

  public Object navigation() {
    return navigation(null);
  }

  public Object navigation(Context context) {
    return navigation(context, null);
  }

 public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
  }

  public void navigation(Activity mContext, int requestCode) {
    navigation(mContext, requestCode, null);
  }

  public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
    ARouter.getInstance().navigation(mContext, this, requestCode, callback);
  }

通过如上的 navigation 可以看到,实际上它们都是最终调用到 ARouter.navigation方法,在没有传入 Context 时会使用 Application 初始化的 Context,并且可以通过 NavigationCallback 对 navigation 的过程进行监听。

  public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
  }

ARouter 仍然只是将请求转发到了 _ARouter

  protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
            // 通过 LogisticsCenter.completion 对 postcard 进行补全
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // ...
    }
    if (null != callback) {
        callback.onFound(postcard);
    }
    // 如果设置了 greenChannel,会跳过所有拦截器的执行
    if (!postcard.isGreenChannel()) {   
            // 没有跳过拦截器,对 postcard 的所有拦截器进行执行
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    } else {
        return _navigation(context, postcard, requestCode, callback);
    }
    return null;
  }

上面的代码主要有以下步骤:

1.通过 LogisticsCenter.completion 对 postcard 进行补全。
2.如果 postcard 没有设置 greenChannel,则对 postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转。
3.如果 postcard 设置了 greenChannel,则直接跳过所有拦截器,调用 _navigation 方法真正实现跳转。

Postcard 的补全

我们看看 LogisticsCenter.completion 是如何实现 postcard 的补全的:

  public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
    // 通过 Warehouse.routes.get 尝试获取 RouteMeta
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
            // 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来
            // 尝试获取 postcard 的 RouteGroup
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
                // ...
            // 如果找到了对应的 RouteGroup,则将其加载进来并重新调用 completion 进行补全
            IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
            iGroupInstance.loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(postcard.getGroup());
            // ...
            completion(postcard);   // Reload
        }
    } else {
            // 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        Uri rawUri = postcard.getUri();
                // 将 uri 中的参数设置进 bundle 中
        if (null != rawUri) {
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();
            if (MapUtils.isNotEmpty(paramsType)) {
                // Set value by its type, just for params which annotation by @Param
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                            params.getValue(),
                            params.getKey(),
                            resultMap.get(params.getKey()));
                }
                // Save params name which need auto inject.
                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }
            // Save raw uri
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }
        // 对于 provider 和 fragment,进行特殊处理
        switch (routeMeta.getType()) {
            case PROVIDER:
                    // 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 provider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                postcard.setProvider(instance);
                // provider 和 fragment 都会跳过拦截器
                postcard.greenChannel();
                break;
            case FRAGMENT:
                     // provider 和 fragment 都会跳过拦截器
                postcard.greenChannel();
            default:
                break;
        }
    }
  }

这个方法主要完成了对 postcard 的信息与 Warehouse 的信息进行结合,以补全 postcard 的信息,它的步骤如下:

1.通过 Warehouse.routes.get根据 path 尝试获取 RouteMeta 对象。
2.若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其loadInto方法将 RouteMeta 加载进 Warehouse,最后调用 completion 重新尝试补全 。
3.将 RouteMeta 的信息设置到 postcard 中,其中会将rawUri 的参数设置进 Bundle。
4.对于 Provider 和 Fragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcardProvider 和 Fragment 都会跳过拦截器。

RouteGroup 的 loadInto 仍然是自动生成的,例如下面就是一些自动生成的代码:

  public void loadInto(Map<String, RouteMeta> atlas) {
  atlas.put("/homework/commit", RouteMeta.build(RouteType.ACTIVITY, HomeworkCommitActivity.class, "/homework/commit", "homework", null, -1, -2147483648));
    // ...
  }

它包括了我们补全所需要的如 Destination、Class、path 等信息,在生成代码时自动根据注解进行生成。

执行跳转

我们看看 navigation 方法是如何实现的跳转:

  private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;
    switch (postcard.getType()) {
        case ACTIVITY:
            // 对 Activity,构造 Intent,将参数设置进去
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            // Set flags.
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            // 切换到主线程,根据是否需要 result 调用不同的 startActivity 方法
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    if (requestCode > 0) {  // Need start for result
                        ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                    } else {
                        ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                    }
                    if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                        ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                    }
                    if (null != callback) { // Navigation over.
                        callback.onArrival(postcard);
                    }
                }
            });
            break;
        case PROVIDER:
                // provider 直接返回对应的 provider
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
                // 对于 broadcast、contentprovider、fragment,构造对象,设置参数后返回
            Class fragmentMeta = postcard.getDestination();
            try {
                Object instance = fragmentMeta.getConstructor().newInstance();
                if (instance instanceof Fragment) {
                    ((Fragment) instance).setArguments(postcard.getExtras());
                } else if (instance instanceof android.support.v4.app.Fragment) {
                    ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                }
                return instance;
            } catch (Exception ex) {
                logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
            }
        case METHOD:
        case SERVICE:
        default:
            return null;
    }
    return null;
  }

可以发现,它会根据 postcard 的 type 来分别处理:

  • 对于 Activity,会构造一个 Intent 并将之前 postcard 中的参数设置进去,之后会根据是否需要 result 调用不同的 startActivity 方法。
  • 对于 Provider,直接返回其对应的 provider 对象。
  • 对于 BroadcastContentProviderFragment,反射构造对象后,将参数设置进去并返回。

可以发现 ARouter 的初始化和路由跳转的整体逻辑还是不难的,实际上就是对 ActivityFragment 的调转过程进行了包装。

Service 的获取

ARouter 除了可以通过 ARouter.getInstance().build().navigation()这样的方式实现页面跳转之外,还可以通过 ARouter.getInstance().navigation(XXService.class)这样的方式实现跨越组件的服务获取,我们看看它是如何实现的:

  public <T> T navigation(Class<? extends T> service) {
    return _ARouter.getInstance().navigation(service);
  }

仍然跳转到了_ARouter 中去实现:

  protected <T> T navigation(Class<? extends T> service) {
    try {
        Postcard postcard = LogisticsCenter.buildProvider(service.getName());
        // Compatible 1.0.5 compiler sdk.
        // Earlier versions did not use the fully qualified name to get the service
        if (null == postcard) {
            // No service, or this service in old version.
            postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        }
        if (null == postcard) {
            return null;
        }
        LogisticsCenter.completion(postcard);
        return (T) postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        return null;
    }
  }

这里首先通过 LogisticsCenter.buildProvider传入service.class 的 name 构建出了一个 postcard。

而在 ARouter 老版本中,并不是通过这样一个完整的 name 来获取 Service 的,而是通过 simpleName,下面为了兼容老版本,在获取不到时会尝试用老版本的方式重新构建一次。

之后会通过 LogisticsCenter.completion 对 postcard 进行补全,最后通过 postcard.Provider 获取对应的 Provider。

除了 buildProvider 之外,其他方法我们已经在前面进行过分析,就不再赘述了:

  public static Postcard buildProvider(String serviceName) {
    RouteMeta meta = Warehouse.providersIndex.get(serviceName);
    if (null == meta) {
        return null;
    } else {
        return new Postcard(meta.getPath(), meta.getGroup());
    }
  }

这里实际上非常简单,就是通过 Warehouse 中已经初始化的 providersIndex 根据 serviceName 获取对应的 RouteMeta,之后根据 RouteMeta的 path 和 group 返回对应的 Postcard

拦截器机制

通过前面的分析,可以发现 ARouter 中存在一套拦截器机制,在 completion 的过程中对拦截器进行了执行,让我们看看它的拦截器机制的实现。

我们先看到 IInterceptor 接口:

  public interface IInterceptor extends IProvider {

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    void process(Postcard postcard, InterceptorCallback callback);
  }

拦截器中主要通过 process 方法完成执行过程,可以在其中对 postcard 进行处理。而拦截器的执行我们知道,是通过InterceptorServiceImpl.doInterceptions 实现的:

  if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
    checkInterceptorsInitStatus();
    if (!interceptorHasInit) {
        callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
        return;
    }
    LogisticsCenter.executor.execute(new Runnable() {
        @Override
        public void run() {
            CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
            try {
                _excute(0, interceptorCounter, postcard);
                interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                    callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                    callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                } else {
                    callback.onContinue(postcard);
                }
            } catch (Exception e) {
                callback.onInterrupt(e);
            }
        }
  
               } else {
                callback.onContinue(postcard);
     }

这里的执行通过一个 Executor 执行,它首先构造了一个值为 interceptors 个数的 CountDownLatch,之后通过 _execute 方法进行执行:

注解处理

那么 ARouter 是如何自动生成 RouteRootRouteMetaProviderGroupProviderInterceptor 的子类的呢?

实际上 ARouter 是通过 AnnotationProcessor 配合 AutoService 实现的,而对于类的生成主要是通过 JavaPoet 实现了对 Java 文件的编写,关于 JavaPoet 的具体使用可以看到其 GitHub 主页https://github.com/xiangjiana/Android-MS

由于注解处理部分的代码大部分就是获取注解的属性,并结合 JavaPoet生成每个 Element 对应的 Java 代码,这块的代码比较多且并不复杂,这里就不带大家去看这部分的源码了,有兴趣的读者可以看看 arouter-complier 包下的具体实现。

总结

ARouter 的核心流程主要分为三部分:
编译期注解处理
通过 AnnotationProcessor 配合 JavaPoet 实现了编译期根据注解对 RouteRootRouteMetaProviderGroupProviderInterceptor 等类的代码进行生成,在这些类中完成了对 Warehouse 中装载注解相关信息的工作。

初始化

通过ARouter.init,可以对ARouter 进行初始化,它主要分为两个步骤:

1.遍历 Apk 的 dex 文件,查找存放自动生成类的包下的类的 ClassName 集合。其中为了加快查找速度,通过一个线程池进行了异步查找,并通过 CountDownLatch 来等待所有异步查找任务的结束。这个查找过程在非 debug 模式下是有缓存的,因为 release 的 Apk 其自动生成的类的信息必然不会变化
2.根据 ClassName 的类型,分别构建 RouteRootInterceptorGroupProviderGroup 的对象并调用了其 loadInto 方法将这些 Group 的信息装载进 Warehouse,这个过程并不会将具体的 RouteMeta 装载。这些 Group 中主要包含了一些其对应的下一级的信息(如 RouteGroup 的 Class 对象等),之后就只需要取出下一级的信息并从中装载,不再需要遍历 dex 文件。

路由

路由的过程,主要分为以下几步:

1. 通过 ARouter 中的 build(path) 方法构建出一个 Postcard,或直接通过其 navigate(serviceClass) 方法构建一个 Postcard。
2. 通过对 Postcard 中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等等。
3.通过 navigation 方法完成路由的跳转,它的步骤如下:

  • a.通过LogisticsCenter.completion 方法根据 Postcard 的信息结合 Warehouse 中加载的信息对 Postcard 的 Destination、Type 等信息进行补全,这个过程中会实现对 RouteMeta 信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理。
  • b.根据补全后 Postcard 的具体类型,调用对应的方法进行路由的过程(如对于 Activity 调用 startActivity,对于 Fragment 构建对象并调用 setArgument)。

4.将 navigation 的结果返回(Activity 返回的就是 null)

顺手留下GitHub链接,需要获取相关面试或者面试宝典核心笔记PDF等内容的可以自己去找
https://github.com/xiangjiana/Android-MS

分享到:
评论

相关推荐

    ARouterDemo源码

    组件化开发推荐目前比较流行的ARouter框架,ARouter是由阿里开发团队开源的组件化框架,目前应用比较多,ARouter经过多年广大开发者测验并改进已经比较完善,选择阿里团队一直有维护更新的ARouter更稳妥一些。

    XyPlayer智能解析源码

    XyPlayer智能解析源码v4.0.8正式版,无需安装,上传即可使用,非常便捷。它支持二次VIP影视解析和屏蔽广告功能。借助XyPlayer正式版源码,你可以轻松搭建专属于自己的影视平台,甚至还能调用第三方资源网站进行播放...

    XyPlayer_v4.1影视解析源码,xyplayer解析官网,PHP源码.rar

    《XyPlayer_v4.1影视解析源码详解与应用》 在当今互联网时代,视频内容已经成为人们休闲娱乐的重要组成部分。XyPlayer_v4.1影视解析源码是一款专为在线视频播放平台设计的解析工具,它能有效地帮助开发者处理视频...

    一次解析源码

    一次解析源码,部分需要更新COOKIE。自己下载后更具里面内容样式抓取吧!

    图片素材解析平台源码

    支持解析网站:千图/90设计/千库/觅元素/包图/摄图/全图/图品汇支持八大网站,其他需要定制 支持千图其他各站图片解析,解析需要配置相应的官方会员cookie。 后台支持批量生产网站会员账号密码以及会员各种权限...

    源码解析alamofire

    源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析...

    易语言源码易语言视频真实地址解析源码.rar

    易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析源码.rar 易语言源码易语言视频真实地址解析...

    XyPlayer智能影视解析系统源码 V4.0.8正式版.zip

    XyPlayer智能解析源码,无需安装,上传即用,非常方便,支持二次VIP影视解析,支持屏蔽广告,XyPlayer正式版源码能让你轻松搭建专属于你的影视平台,甚至还能调用第三方的资源网站来进行播放。本次为大家带来...

    素材资源解析系统源码 代下程序 第三方平台下载站程序千图网千库网等素材网站下载网站.zip

    素材资源解析系统源码 代下程序 第三方平台下载站程序千图网千库网等素材网站下载网站, 1.需要目标站会员解析 2.随着目标站规则的更改,解析接口可能会失效—接口不保证长期有效,不会修改的最好别买,因为有些...

    一次解析源码外面俗称3500元一套

    【标题】"一次解析源码"通常指的是在软件开发中,对原始的编程代码进行一次性编译或解析,形成可以直接运行或使用的程序。这个过程不涉及对已有的编译结果进行再次解析,而是直接处理源代码,使得用户能够更深入地...

    vue源码解析.zip

    在深入学习Vue.js时,理解其源码是非常重要的一步,可以帮助开发者更好地掌握其工作原理,从而优化应用性能和解决实际问题。下面我们将详细探讨"vue源码解析.zip"中的关键知识点。 首先,我们来看"Vue源码探秘之...

    素材解析源码.亲测接口正常

    【素材解析源码】是一种用于解析和处理网络素材的编程代码,主要应用于网站开发中。在互联网上,素材包括但不限于图片、音频、视频、文本等,这些资源在网页设计和内容展示中起着至关重要的作用。源码是程序的基础,...

    易语言源码易语言百度云盘解析源码.rar

    易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码易语言百度云盘解析源码.rar 易语言源码...

    无广告超清解析源码需要的自行下载

    全新无广告加速解析源码播放器开源无加无授权,网页播放器,带弹幕,记忆播放等 (A cat player, open source, without authorization, web player, with screen, memory player, etc)

    易语言源码易语言百度网盘解析源码.rar

    易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码易语言百度网盘解析源码.rar 易语言源码...

    千图等12网素材解析源码程序.rar

    千图等12网素材解析源码程序 支持解析网站:千图/90设计/千库/觅元素/包图/摄图/全图/图品汇等网站 支持千图其他各站图片解析,解析需要配置相应的官方会员cookie。 后台支持批量生产网站会员账号密码以及会员...

    ANDROID SDK 源码解析

    ANDROID SDK 源码解析 ANDROID SDK 源码解析 ANDROID SDK 源码解析

    AT 命令接收和解析源码

    AT命令简易解析 AT命令的应用场合很多,也是应用最为通用的命令。而AT命令并不像通信协议特定的数据帧格式,解析AT命令需要匹配每个字符。AT命令一般有三种状态:查询、执行、设置,根据这三种状态,本文将使用一种...

    CMarkup源码 XML创建及解析

    简单实用的XML解析源码,开发的时候用到所以研究了下,现在分享出来给大家。 下载后取出里面的Markup.cpp和Markup.h,导入你的工程里面,CMarkup类就可以用了。其中的MSXML是另一套解析源码,主要使用COM,大家可以...

    mybatis 3.x源码深度解析与最佳实践.html

    mybatis 3.x源码深度解析与最佳实践.html

Global site tag (gtag.js) - Google Analytics