Alibaba-ARouter 源码分析笔记

本文章用于记录笔者学习 ARouter 源码的过程,仅供参考,如有错误之处还望悉心指出,一起交流学习。

ARouter 是 Alibaba 开源的一款 Android 页面路由框架,特别适用于模块化开发过程中,页面跳转所带来的耦合问题。区别于 Android 提供的隐式跳转,ARouter 还提供:** 更简便灵活的配置方式 ** 、** 可控制的跳转过程(拦截器) ** 、** 可监听/处理跳转结果 ,正是这些特性打造了这么一个 更好用的轮子 **。

正是这些也让笔者对这个框架产生的极大的兴趣,迫不及待的下载了源码想要一探究竟。在对该框架有了初步了解以后,笔者觉得可从以下几个入口进一步分析 ARouter 的源码。

目录

● ** ARouter 编译过程分析 **

● ** ARouter init 初始过程分析 **

● ** ARouter 运行时 API 调用过程分析 **

ARouter 编译过程分析

首先我们知道 ARouter 的自动注册的实现是利用了编译期自定义注解的处理来完成的。我们先来看看 ARouter 定义了哪些自定义注解(此部分源码位于 arouter-annotation):
1.@Route 是 ARouter 最重要的注解,也是路由最基本的节点,使用该注解标注的类将被自动添加至路由表中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();

    /**
     * Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
     */
    String group() default "";

    /**
     * Name of route, used to generate javadoc.
     */
    String name() default "undefined";

    /**
     * Extra data, can be set by user.
     * Ps. U should use the integer num sign the switch, by bits. 10001010101010
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * The priority of route.
     */
    int priority() default -1;
}

Route 使用 path 来唯一标识一个路由节点,而 group 用于将路由节点进行分组,实现按组动态加载(后文会有详细描述)
值得说明的一点是 ARouter 并非仅提供页面(Activity)的路由功能,还可以用来路由模块想要暴露给其他模块调用的接口。也就是说 @Route 不仅可用于 Activity 类,还可用于模块对外接口的实现类,实现类似于 AIDL 的功能。请看 Route 的类型定义类 RouteType

public enum RouteType {
    ACTIVITY(0, "android.app.Activity"),
    SERVICE(1, "android.app.Service"),
    PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
    CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
    BOARDCAST(-1, ""),
    METHOD(-1, ""),
    FRAGMENT(-1, "android.app.Fragment"),
    UNKNOWN(-1, "Unknown route type");
}

从源码分析,ARouter 将会提供功能将会更加强大。但就目前源码而言,仅实现了 ACTIVITYPROVIDER 这两种类型,也就是目前 可用于@Route 标识的类型。这里我们先留下一个问题,** 编译期注解处理过程中是如何区分类型的?**

2.@Interceptor 是拦截器注解,拦截器是全局的。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
    /**
     * The priority of interceptor, ARouter will be excute them follow the priority.
     */
    int priority();

    /**
     * The name of interceptor, may be used to generate javadoc.
     */
    String name() default "Default";
}

这里我们再留一个问题,** 拦截器是如何按照优先级依次工作的?**

3.@Autowired 是页面跳转时参数传递用的。Activity 中使用该注解标志的变量,会在页面被路由打开的时候自动赋予传递的参数值。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {

    // Mark param's name or service name.
    String name() default "";

    // If required, app will be crash when value is null.
    // Primitive type wont be check!
    boolean required() default false;

    // Description of the field
    String desc() default "No desc.";
}

我们依然有疑问,** 自动赋值的过程是如何完成的? **

以上就是当前 ARouter 框架中用到所有注解类型了。想要依次解开我们上面留下的疑问,看来我们还要进一步继续分析源码了。

我们先来看一下编译期 ARouter 的产物吧,下载源码并成功编译一边,我们就能看到 apt 为我们生成了如下几个源文件

Paste_Image.png

我们依次具体来看看都生成了什么 (先看com.alibaba.android.arouter.routes)

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$service implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    atlas.put("/service/test", RouteMeta.build(RouteType.PROVIDER, TestService.class, "/service/test", "service", null, -1, -2147483648));
  }
}
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); put("url", 18); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 18); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(7, Test1Interceptor.class);
  }
}
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    providers.put("IProvider", RouteMeta.build(RouteType.PROVIDER, TestService.class, "/service/test", "service", null, -1, -2147483648));
  }
}
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("service", ARouter$$Group$$service.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

很明显,ARouter$$Group$$serviceARouter$$Group$$test分别是分组 service 和 test 的组内路由清单列表,我们可以把它想象成分别是 1班2 班 的学生名单。
ARouter$$Root$$app 则是组别的清单列表,我们亦可以把它想成班级的清单,里面有两个班级,1班2 班
与之类似的,ARouter$$Interceptors$$app 是拦截器的清单列表,ARouter$$Providers$$app 则是 PROVIDER 类型的路由节点的清单列表。

特别的,请注意:PROVIDER 类型的路由节点既存在于存在于对应的分组中,也存在于该类型的清单列表中,这就好像学生中有一些是少先队员,每个班级中都有可能有少先队员,而学校又有一份少先队员的总列表。这就意味着我们有两种方式来查找到一个可确定的少先队员。所以,ARouter 可通过两种方式来获取 PROVIDER 类型的路由节点。

现在我们再来看看 BlankFragment$$ARouter$$Autowired

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class BlankFragment$$ARouter$$Autowired implements ISyringe {
  @Override
  public void inject(Object target) {
    BlankFragment substitute = (BlankFragment)target;
    substitute.name = substitute.getArguments().getString("name");
  }
}

哦~~~,原来自动赋值就是靠它帮我们做的,而且看起来想要使用自动赋值,变量不能为 private 类型,那么现在问题就变成** 这个帮助类是在什么时候调用的呢?**,这个问题肯定要到运行时 API 的调用才能知道了。

看完这些编译的产物,我们先来做一下简单的总结与猜想:

  1. ARouter 的自动注册机制一定是通过加载这些路由清单文件来实现的
  2. 我们可以通过两种方式来找到定义的 PROVIDER 类型的路由节点
  3. 自动赋值功能的实现,一定是在页面被路由打开时调用了生成的帮助类(ISyringe接口的 inject(Object target) 方法)

剩下的便是自定义注解的解析与代码生成过程,这部分代码在 arouter-compiler 中,这个过程大家可以参考此类型的文章进行了解,这里我们就不再赘述。
等等,我们还差点忘了一件事,我们前面留了一个问题 编译期注解处理过程中是如何区分类型的? ,这个问题我们还没搞清楚呢。
火速定位到 com.alibaba.android.arouter.compiler.processor.RouteProcessor
果然不出我们所料,找到如下代码

// public static final String ACTIVITY = "android.app.Activity";
TypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY);
// public static final String SERVICE = "android.app.Service";
TypeElement type_Service = elementUtil.getTypeElement(SERVICE);
// Consts.IPROVIDER = "com.alibaba.android.arouter.facade.template.IProvider";
iProvider = elementUtil.getTypeElement(Consts.IPROVIDER).asType();
if (typeUtil.isSubtype(tm, type_Activity.asType())) {                 // Activity
    logger.info(">>> Found activity route: " + tm.toString() + " <<<");

    // Get all fields annotation by @Autowired
    Map<String, Integer> paramsType = new HashMap<>();
    for (Element field : element.getEnclosedElements()) {
        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !typeUtil.isSubtype(field.asType(), iProvider)) {
            // It must be field, then it has annotation, but it not be provider.
            Autowired paramConfig = field.getAnnotation(Autowired.class);
            paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), TypeUtils.typeExchange(field.asType()));
        }
    }
    routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else if (typeUtil.isSubtype(tm, iProvider)) {         // IProvider
    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
    routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);
} else if (typeUtil.isSubtype(tm, type_Service.asType())) {           // Service
    logger.info(">>> Found service route: " + tm.toString() + " <<<");
    routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
}

没错啦,原来是看是否为 Activity 、IProvider 、 Service 的子类来判断的。所以有一点很重要,要想成功注册一个 PROVIDER 类型的路由节点,一定要实现 com.alibaba.android.arouter.facade.template.IProvider 这个接口

ARouter init 初始化过程分析

经过前面的分析,我们几乎可以断定,ARouter 的初始化过程最重要的一步一定是把前面编译产生的路由清单文件加载到内存,形成一个路由表,以供后面路由查找之用。
我们来看 arouter-api 中的 com.alibaba.android.arouter.launcher.ARouter

/**
* 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.");
    }
}

我们发现它实际上去调用了 _ARouterinit 方法,其他方法也一样,最终都是调用 _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;
}

_ARouter 又调用了 LogisticsCenter.init,在这之前,_ARouterARouter 中都有 hasInit 这个变量,笔者觉得该变量既是静态的,且功能相同,何不共同使用一个就好?

/**
 * LogisticsCenter init, load all metas in memory. Demand initialization
 */
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        // These class was generate by arouter-compiler.
        List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

        //
        for (String className : classFileNames) {
            if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                // This one of root elements, load root.
                ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
            } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                // Load interceptorMeta
                ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
            } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                // Load providerIndex
                ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
            }
        }

        if (Warehouse.groupsIndex.size() == 0) {
            logger.error(TAG, "No mapping files were found, check your configuration please!");
        }

        if (ARouter.debuggable()) {
            logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
        }
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

没错了,ARouter 的初始化只做了一件事,找到自己编译期产生的清单文件,把 Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。也就是这些:

QQ截图20170319174528.png

ARouter 初始化阶段仅载入了 Group 清单,并没有具体载入每个 Group 中包含的具体的路由节点清单,这就与 ARouter 的官方说明一致了:映射关系按组分类、多级管理,按需初始化,只有我们使用到具体的 Group 时,才会加载对应的 Group 列表。
最后我们再看一眼 Warehouse

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    //......
}

从这我们可以看出,Warehouse 不仅帮我们保存了路由清单,还给我们提供了缓存,也就是说 同一个目标(RouteMeta、IProvider、IInterceptor)仅会在第一次使用的时候创建一次,然后缓存起来,之后查找都是直接使用缓存的对象

ARouter 运行时 API 调用过程分析

我们先看一下官方文档提供给我们的使用方法:

// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();

以上为页面跳转的调用方法,我们再看一下 Provider 服务的获取方法:

// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();

果然 Provider 服务可以通过两种方式去获取,也就符合了我们前面的判断。
我们再进一步跟踪调用过程,很快我们就发现调用过程,最后回到了 _ARouter 的两个方法上:

// byType
protected <T> T navigation(Class<? extends T> service) {
    // .............
}
// byName
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
    // .............
}

我们先来分析简单一点的 byType 方式

protected <T> T navigation(Class<? extends T> service) {
    try {
        Postcard postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        LogisticsCenter.completion(postcard);
        return (T) postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        return null;
    }
}

ARouter 中把所有的路由请求都封装成了 Postcard 对象,这个类也贯穿了接下来的整个路由过程。

public final class Postcard extends RouteMeta {
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to tranform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second !
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannal;

    // copy from RouteMeta 
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    // ......
}

可以看到 Postcard 包含了众多的属性值,可以说是提供了路由过程中所有的控制变量,我们现在要了解服务查找的 byType 方式,从注释中不难看出,最后这个服务会被赋值给 provider 属性。那我们就来看这个值是何时又是如何 set 给 provider 的。
回到我们的 navigation 方法,核心代码只有三行,

// 1. 通过 className 拿到 Postcard 对象
Postcard postcard = LogisticsCenter.buildProvider(service.getSimpleName());
// 2. 对 Postcard 对象进行处理
LogisticsCenter.completion(postcard);
// 3. 返回 Postcard 中的 provider 属性值
return (T) postcard.getProvider();

毫无疑问,第一步通过服务接口类名称,从仓库的服务路由表中查找到服务的信息,创建一个 Postcard 对象

/**
 * Build postcard by serviceName
 *
 * @param serviceName interfaceName
 * @return postcard
 */
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());
    }
}

我们再来看处理 Postcard 的过程

/**
 * Completion the postcard by route metas
 *
 * @param postcard Incomplete postcard, should completion by this method.
 */
public synchronized static void completion(Postcard postcard) {
    // 1. 查找仓库的路由缓存,看是否已在缓存中
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
        // 2. 查找仓库的分组清单中是否存在该分组
        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 {
            // Load route and cache it into memory, then delete from metas.
            try {
                // 将分组中所有的路由节点加载进仓库缓存中
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);
                // 从分组列表删除已加载的分组,防止重复加载
                Warehouse.groupsIndex.remove(postcard.getGroup());
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
            // 重新查找
            completion(postcard);   // Reload
        }
    } else {
        // 缓存中已存在,从缓存读取各属性值
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must be implememt IProvider
                // 查找服务缓存中是否已有该服务,若没有则创建一个,并加入缓存
                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);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            default:
                break;
        }
    }
}

这里面,明明是从服务列表(Warehouse.providersIndex)中查找到的服务却又从 Group 分组中重新查找一遍,这一点笔者觉得可以优化。

接下来,我们继续分析 byName 方式
这种方式的 API 方法的参数表中就已经有 Postcard 对象了,这个 Postcard 对象是怎么来的呢?

protected Postcard build(String path) {
    if (StringUtils.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));
    }
}

/**
 * Build postcard by path and group
 */
protected Postcard build(String path, String group) {
    if (StringUtils.isEmpty(path) || StringUtils.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);
    }
}

/**
 * Extract the default group from path.
 */
private String extractGroup(String path) {
    if (StringUtils.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 (StringUtils.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;
    }
}

可以通过 path 来创建,而未指定 Group 的情况下,是使用 path 的第一个“/”符号之前的值。

/**
 * Use router navigation.
 *
 * @param context     Activity or null.
 * @param postcard    Route metas
 * @param requestCode RequestCode
 * @param callback    cb
 */
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
    try {
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // 通过 callback 回调失败结果
        // callback为空的情况下,如果有定义全局的降级处理(DegradeService),则使用全局处理
        // ......

        return null;
    }

    if (null != callback) {
        callback.onFound(postcard);
    }

    if (!postcard.isGreenChannal()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        // 拦截器处理
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            /**
             * Continue process
             *
             * @param postcard route meta
             */
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode);
            }

            /**
             * Interrupt process, pipeline will be destory when this method called.
             *
             * @param exception Reson of interrupt.
             */
            @Override
            public void onInterrupt(Throwable exception) {
                logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
            }
        });
    } else {
        return _navigation(context, postcard, requestCode);
    }

    return null;
}

这里终于出现了拦截器的处理,通过调用 InterceptorService 来处理拦截器逻辑,这个我们稍后再回过来看,我们先继续往下看路由实现:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode) {
    Context currentContext = null == context ? mContext : context;

    switch (postcard.getType()) {
        case ACTIVITY:

            // Build intent
            Intent intent = new Intent(currentContext, postcard.getDestination());
            // postcard.getExtras() 中为传递的参数
            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);
            }

            // Judgment activity start type.
            if (requestCode > 0) {  // RequestCode exist, need startActivityForResult, so this context must son of activity.
                ((Activity) currentContext).startActivityForResult(intent, requestCode);
            } else {
                currentContext.startActivity(intent);
            }

            break;
        case PROVIDER:
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
        case METHOD:
        case SERVICE:
        default:
            return null;
    }

    return null;
}

到这里我们看到仅目前仅实现了 ACTIVITYPROVIDER 两种类型。
好了,我们再回过头去看看拦截器的实现 com.alibaba.android.arouter.core.InterceptorServiceImpl

@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    // 检查拦截器列表是否为空
    if (CollectionUtils.isNotEmpty(Warehouse.interceptors)) {
        // 等待拦截器服务完成初始化
        // ......

        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                // CountDown倒数计数器
                CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                try {
                    // 在另一个线程中递归依次执行拦截器操作
                    _excute(0, interceptorCounter, postcard);
                    // 等待超时
                    interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                    // 倒数计时器不为0,表示还有拦截器未执行完成
                    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);
    }
}

/**
 * Excute interceptor
 *
 * @param index    current interceptor index
 * @param counter  interceptor counter
 * @param postcard routeMeta
 */
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
    if (index < Warehouse.interceptors.size()) {
        IInterceptor iInterceptor = Warehouse.interceptors.get(index);
        iInterceptor.process(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
                // Last interceptor excute over with no exception.
                counter.countDown();
                _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
            }

            @Override
            public void onInterrupt(Throwable exception) {
                // Last interceptor excute over with fatal exception.

                postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                counter.cancel();
            }
        });
    }
}

而拦截器服务的初始化过程,就是依次调用每个拦截器 init 方法。
到这里,拦截器的实现算是水落石出了,但笔者还是有个疑问:
看到这里都没有见过拦截器有按照优先级进行过排序,那是如何保障拦截器是依优先级依次执行的呢?除非清单列表生成的时候就已经排好序了?
果然,不出笔者所料,在编译期生成清单列表的时候,拦截器就已经按照优先级排好序了,但是注解的解析代码中并没有进行任何的排序操作,这也是笔者疑惑之处,难道拦截器的注解在扫描的时候就已经按照优先级排列好了吗?

最后,我们还忘记了一个问题,页面自动赋值的帮助类是在什么时候调用的呢?
我们前面梳理一遍代码下来,并没有发现有自动调用呀,那么肯定是要手动调用咯?
回到官方的使用手册上,我们找如下内容:

 // 为每一个参数声明一个字段,并使用 @Autowired 标注
 // URL中不能传递Parcelable类型数据,通过ARouter api可以传递Parcelable对象
 @Route(path = "/test/activity")
 public class Test1Activity extends Activity {
     @Autowired
     public String name;
     @Autowired
     int age;
     @Autowired(name = "girl") // 通过name来映射URL中的不同参数
     boolean boy;
     @Autowired
     TestObj obj;    // 支持解析自定义对象,URL中使用json传递

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         ARouter.getInstance().inject(this);

         // ARouter会自动对字段进行赋值,无需主动获取
         Log.d("param", name + age + boy);
     }
 }

那就没错了,需要我们手动调用 ARouter.getInstance().inject(this);
来看一下里面是什么吧

static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
    }

最后的实现是在 com.alibaba.android.arouter.core.AutowiredServiceImpl

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {

    // ......

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                autowiredHelper.inject(instance);
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            // ARouter.logger.error(TAG, "Autowired made exception, in class [" + className + "]");
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

是它去找到了编译期生成的帮助类,完成了赋值操作

后记

看到这里,整个 ARouter 框架的核心实现都差不多了,更多的细节大家可以下载源码仔细研究,希望本文对大家有帮助,如有错误之处,涵请指出,多多指教,感激不尽。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容