当前位置: 首页 > 工具软件 > ARouter > 使用案例 >

ARouter原理

柴辰阳
2023-12-01

一、编译过程

arouter-compiler模块用于处理注解,编译期生成类文件,annotationProcessor 'com.alibaba:arouter-compiler:latestversion'即是引入的这个module。annotationProcessor的作用是在编译期处理注解,并不会打包进apk。

1、注解

我们在要跳转的Activity上面添加了@Route注解,ARouter 的自动注册是利用了编译期自定义注解的处理来完成的,我们看一下这个注解的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route{
    String path();
    String group() default "";
    String name() default "";
    int extras() default Integer.MIN_VALUE;
    int priority() default -1;
}

Route 使用 path 来唯一标识一个路由节点,而 group 用于将路由节点进行分组,实现按组动态加载,@Route 不仅可用于 Activity 类,还可用于模块对外接口的实现类,实现类似于 AIDL 的功能。
APT就是注解处理器,它的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。
自己构造APT,需要写一个类继承AbstractProcessor然后重写它的方法,ARouter的注解处理器是RouteProcessor

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
...
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        ...
    }

这里面调用了其父类BaseProcessor的init方法

public abstract class BaseProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        ...
        // Attempt to get user configuration [moduleName]
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(KEY_MODULE_NAME);
            generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
        }

BaseProcessor的init方法中获取每个模块的moduleName并存入map集合options。
RouteProcessor中的process方法是处理注解方法的地方,在这个方法中会获取所有Route注解的元素:

public class RouteProcessor extends BaseProcessor {
...
@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                this.parseRoutes(routeElements);
...
    }

process方法中获取所有注解了Route的元素放到set集合routeElement中,并把这个当作参数传入this.parseRoutes方法中。这个方法会调用poet库生成Java文件,他生成了两个类,一个是ARouter xxx implement IRouterGroup,一个是ARouter xxx implement IRouteRoot。
看下编译后生成的文件

ARouter Group
public class ARouter$$Group$$arouter implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", null, -1, -2147483648));
    atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", null, -1, -2147483648));
  }
}

这个类实现了IRouterGroup接口,实现了loadInto方法,路径名path为key,RouteMeta为value放入map集合atlas中,其中RouteMeta是存放了@Route注解修饰的元素的一些重要信息,有了这个,我们后续可以通过intent来跳转到这个Activity了。这个方法调用之后,activity就完成了注册。

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

实现了IRouteRoot接口,它的loadInto方法中,以路径名后面第一个斜杠后面的内容Group为key,以上一个类IRouterGroup类为value放入集合routes中。可以通过Group来获取到IRouterGroup的实现类,通过这个实现类拿到Activity.class。

总结

在apt阶段,生成的class类有Arouter$$Root$$模块名,模块名是在gradle中配置的arg("AROUTER_MODULE_NAME", "${project.getName()}")属性,把所有组的信息放到传进来的map中,这个组是通过我们在Route注解的path属性拆分的,比如定义/login/loginActivity,会认为组名是login,Arouter$$Root$$组名放的是该组下,所有的路由表信息,包括route、provider注解通过RouteMeta包装对应的class类信息,provider注解会放在Arouter$$Providers$$模块名下面。
再来梳理一下,ARouter发现服务是这样的,如果有一个类HelloService继承IProvider,并且HelloService有path的注解的话

@Route(path = "/module1/hello")
public class HelloService implements IProvider {
    @Override
    public void init(Context context) {

    }
}

ARouter会生成这样的类文件,module1是模块名,可以看到往传进来的一个Map中放入了一个以HelloService为key,对应的RouteMeta为value的键值对。RouteMeta可以理解为跳转的元信息,可以通过RouteMeta获取跳转的path,group以及对应的目标Class等:

public class ARouter$$Providers$$module1 implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.example.module1.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloService.class, "/module1/hello", "module1", null, -1, -2147483648));
  }
}
问题
  1. ARouter也可用于获取服务,假设采用通过接口的方式发现服务的话,如果接口不止一个实现,会怎样
    一个类比如IUserService继承IProvider接口,然后编写类UserServiceImpl实现IUserService,这时候放入Map的key就不再是实现的UserServiceImpl类名,而是其所实现的接口IUserService,如果还有一个类比如UserServiceImpl2同样继承IUserService,并且UserServiceImpl2同样也有path注解,会生成这样类文件
public class ARouter$$Providers$$module1 implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.example.module1.IUserService", RouteMeta.build(RouteType.PROVIDER, UserServiceImpl1.class, "/u/1", "u", null, -1, -2147483648));
    providers.put("com.example.module1.IUserService", RouteMeta.build(RouteType.PROVIDER, UserServiceImpl2.class, "/u/2", "u", null, -1, -2147483648));
  }
}

同样实现IUserService会使它们有相同的key,后面put的元素会覆盖掉之前put的元素。如果接口不止一个实现,字母表排在后面的接口会覆盖掉排在前面的接口。

  1. 为什么不能用抽象类继承IProvider然后实现抽象类而只能用接口继承IProvider然后实现该接口
    这是因为ARouter在RouteProcessor的parseRoutes方法中只处理了接口的情况。

  2. 如果我们注解相同的path会怎么样?即有一个SecondActivity使用/a/b的path,而另一个ThirdActivity也使用/a/b的path。
    (1)如果SecondActivity和ThirdActivity在同一个module下:RouteProcessor有一个成员变量groupMapprivate Map<String, Set<RouteMeta>> groupMap = new HashMap<>();key是string,即group的名字,value是一个Set,当试图放入一个Set中已有的元素时,会放入不了,并且不会抛异常,如果我们在同一个module中注解相同的path,那么排在字母表后面的元素会无效,ThirdActivity由于没有被添加到Set中因此不会再生成的文件中出现
    (2)如果SecondActivity和ThirdActivity在不同的module下:由于apt框架是分module编译,并且每个module都会生成ARouter$$Root$module_nameARouter$$Group$$group_name等文件,那么它们都会生成ARouter$$Group$$a的文件,那么在合并到dex的时候肯定会出错,这时候根本就编译过不了

二、初始化init

初始化代码ARouter.init(this)init过程就是把所有注解的信息加载内存中,并且完成所有拦截器的初始化。

    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.afterInit方法,真正调用的还是_ARouter类的方法
看_ARouter.init方法:

    protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }

最终调用了LogisticsCenter的init方法,传入线程池executor。并且拿到主线的looper给了mHandler,看LogisticsCenter的init方法:

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;
 
    try {
        //如果通过插件获取路由表信息,则该方法registerByPlugin=false
        loadRouterMap();
        if (registerByPlugin) {
        } else {
            Set<String> routerMap;
 
            // 如果是debuggable=true或者有新的版本
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                // 加载前缀为com.alibaba.android.arouter.routes的class类,放到set集合里面
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                //将过滤到的class类放到sp中,方便下次直接取
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }
                //更新下最新的版本
                PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
            } else {
                //直接从sp中取过滤到的class类
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }
 
 
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到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() + "]");
    }
}

init方法中做了几件事:

  • 通过loadRouterMap方法判断是不是通过arouter-register自动加载路由表,如果是通过自动加载的则registerByPlugin=true
  • 紧接着通过ClassUtils.getFileNameByPackageName(此处用到了线程池、CountDownLatch面试高频考点)获取到apk中前缀为com.alibaba.android.arouter.routes的类,这里面主要是通过判断是不是支持MultiDex,如果不支持MultiDex,扫描所有的dex文件,然后压缩成zip文件,然后通过DexFile.loadDex转化成DexFile对象,如果支持MultiDex,直接new DexFile,然后循环DexFile拿里面的class文件,然后过滤出com.alibaba.android.arouter.routes前缀的class并返回。(方法做的就是找到app的dex,然后遍历出其中的属于com.alibaba.android.arouter.routes包下的所有类名,打包成集合返回)
  • 拿到了需要的class类后,放到sp里面,方便下次不去扫描apk拿class,更新版本号
  • 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
  • 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
  • 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中

拿到所有生成类名的集合后,通过反射实例化对象并调用方法,将注解的一些元素添加到static集合中

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

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}
  • IRouteRoot的实现将有@Route注解的module名添加到参数集合中,也就是groupsIndex。
  • IInterceptorGroup的实现将@Interceptor注解的类添加到参数集合中,也就是interceptorsIndex中。
  • IProviderGroup的实现将继承自IProvider的类添加到参数集合中,也就是providersIndex中。

三、build

_ARouter.afterInit方法里面会去拿InterceptorServiceImpl

    static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

ARouter.getInstance().build("/arouter/service/interceptor")会返回PostCard对象,并给PostCard的group和path属性赋值为arouter、/arouter/service/interceptor

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

PathReplaceService,它是继承IProvider的接口,它是预留给用户实现路径动态变化功能。extractGroup方法截取路径中的第一段作为分组名。build方法会创建并返回一个Postcard(明信片)对象,将path,group和bundle传入Postcard中。

四、navigation

navigation有很多重载的方法,最终都会走_Arouter.navigation,其中navigation里面也有两种形式获取到路由表类,先看activity常规的形式
(省略provider部分逻辑和_navigation部分代码,后面再说)

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //这也是个路由表,通过另外一种形式获取PretreatmentService的实例
    PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
    //如果onPretreatment返回false就是自己处理路由逻辑,不往下走了
    if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
        return null;
    }

    try {
        //最终会走这里
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        ...
        if (null != callback) {
            callback.onLost(postcard);
        } else {
            // 获取DegradeService的实例
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }

        return null;
    }
    //省略provider部分逻辑和_navigation部分代码,后面再说
    return null;
}

首先获取PretreatmentService类型的路由表,怎么得到InterceptorService实例的,我们直接看LogisticsCenter.completion:

public synchronized static void completion(Postcard postcard) {
    //第一次进来是拿不到RouteMeta信息的,因为routes是空的
    //先去Warehouse的routes这个Map去找有没有相应的RouteMeta,如果没有的话,就去Warehouse的groupsIndex加载该group,然后这个时候routes这个map肯定有相应的RouteMeta了。
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        //我们传过来的postcard的group是arouter、path是/arouter/service/interceptor
        //我们在groupIndex中找对应的groupMeta,其实看到这的时候,我们默认是没有root为arouter的组,只能去arouter默认提供的root中找
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
        } else {
            try {
                //反射拿到ARouter$$Group$$arouter
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                //所以最终把InterceptorServiceImpl放到了Warehouse.routes中
                iGroupInstance.loadInto(Warehouse.routes);
                //用完groupsIndex对应的IRouteGroup信息后,从map中移除掉,下次就直接从routes中去拿了
                Warehouse.groupsIndex.remove(postcard.getGroup());

            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
            //继续走一遍completion,下次会走下面的else
            completion(postcard);
        }
    } else {
        //对postCard属性赋值
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        Uri rawUri = postcard.getUri();
        //默认uri为空
        if (null != rawUri) {  
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();

            if (MapUtils.isNotEmpty(paramsType)) {
                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());
        }

        switch (routeMeta.getType()) {
            //由于InterceptorServiceImpl是provider类型的
            case PROVIDER:
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                //拿对应的provider
                //从Warehouse的providers这个map中去获取class对应的对象,如果有的话,就直接使用,如果没有的话,就通过反射创建一个实例,并添加到Warehouse的providers这个map中。所以每次通过ARouter的接口的方式发现服务,每次获取的都是同一个对象。
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) {
                    IProvider provider;
                    try {
                        //反射创建InterceptorServiceImpl
                        provider = providerMeta.getConstructor().newInstance();
                        //调用InterceptorServiceImpl的init方法
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                //给postcard赋值
                postcard.setProvider(instance);
                postcard.greenChannel();
                break;
            case FRAGMENT:
                postcard.greenChannel();
            default:
                break;
        }
    }
}

completion方法中,使用postcard的path,去path与跳转目标的map中获取routeMeta对象,从而获取其中的destination。postCard封装了跳转的destination目标,如果postCard的type为activity,则创建intent,将postCard中的bundle传入intent的extras,调用startActivity进行跳转。如果type为fragment,则通过postCard拿到目标fragment类,通过反射获取该Fragment实例。

回到_ARouter的navigation方法的下半部分代码,

//如果是`InterceptorServiceImpl`类型的postcard.isGreenChannel()是true,除非是activity或fragment类型的
if (!postcard.isGreenChannel()) {
    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);
}

在_ARouter的navigation跳转之前使用doInterceptions去做拦截处理:

public interface InterceptorService extends IProvider {
    void doInterceptions(Postcard postcard, InterceptorCallback callback);
}

看_navigation方法,实际上的跳转还是通过intent

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:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                // Set flags.
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }
                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
        case PROVIDER:
            return postcard.getProvider();

    }

    return null;
}
 类似资料: