ARouter的使用及其原理解析

ARouter的使用及其原理

在阅读这篇文章之前,如果对APT注解处理器和poet框架不了解的,可以参考以下两篇博客:

Poet使用详解

注解处理器实战

ARouter介绍

  • ARouter是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。它可以实现组件间的路由功能。路由是指从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。这里可以体现出路由跳转的特点,非常适合组件化解耦。

ARouter的简单使用

现在我们有两个module,一个是app主工程,一个是login组件,从app组件跳转到login组件需要使用ARouter。

在使用ARouter之前我们要进行ARouter和注解依赖的注入,以及ARouter在application’中的初始化,然后才可以正式开始使用,比如我们要在刚才说的app组件通过点击按钮跳转到login组件我们可以这么写:

  1. 在login组件对应的Activity上面添加注解@Route(path = “/login/login1”)

    @Route(path = "/login/login1")
    public class Login extends AppCompatActivity {
    	//......
        }
    }
    
  2. 在app组件中,点击事件进行如下书写:

    login.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ARouter.getInstance().build("/login/login1").navigation();
        }
    });
    

只需要这样操作,两个模块就不用相互有任何直接的依赖,直接就可以进行跳转,模块与模块之间就相互独立了。

至此,ARouter的使用,就是分为三步:

  • 添加依赖初始化
  • 为跳转的目标Activity设置注解,参数设置为路径
  • 在准备跳转的Activity中,调用ARouter.getInstance().build(“xxxxxx”).navigation()方法即可

说完了用法,我们就来说一下ARouter的源码处理。

ARouter的跳转原理

注解&APT

在使用过程中,我们在目标Activity上面添加了@Router()注解,我们看一下这个注解的定义:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route{
    String path();
    ……
}

Route里面有一个主要的参数path,这个就是表示了Activity的路由地址。

而APT就是注解处理器,它的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。

自己构造APT,需要写一个类继承AbstractProcessor然后重写它的方法,比如init()…

注解处理器RouteProcessor

ARouter的注解处理器是RouteProcessor

@AutoService(Processor.class)		//这个库为ARouter的注解处理器APT实现了自动注册
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})	//表明了当前APT是处理那些注释的
public class RouteProcessor extends BaseProcessor {
    super.init(processingEnv);
    ......
}

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

@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注解的元素:

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

在第四行中,process方法中获取所有注解了Route的元素放到set集合routeElement中,并把这个当作参数传入this.parseRoutes方法中。这个方法会调用poet库生成Java文件,他生成了两个类,一个是ARouter G r o u p Group Groupxxx implement IRouterGroup,一个是ARouter R o o t Root Rootxxx implement IRouteRoot。下面我们一一来说明:

ARouter Group xxx

看一下例子:

public class ARouter$$Group$$login implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/login/login1", 
              RouteMeta.build(RouteType.ACTIVITY, 
                              Login.class, 
                              "/login/login1", 
                              "login", 
                              null, 
                              -1, 
                              -2147483648));
  }
}

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

ARouter Root xxx

看一下例子:

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

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

接下来回过头看一下是如何生成的IRouterGroup类和IRouteRoot类

this.parseRoutes(routeElements)方法

这个方法的参数是所有注解了Route的元素。

这个方法中有这样一段代码:

MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)		//添加@Override注释
                    .addModifiers(PUBLIC)				//方法为public
                    .addParameter(rootParamSpec);		//添加一个参数

这个类通过poet框架中的MethodSpec类来实现方法构造,这里构造了loadInto方法,调用3个add为这个方法添加了一些设置,如注释,方法访问权限和参数。构造之后,进入了一层for循环:

for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMeta;
                // Activity or Fragment
                if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                    Map<String, Integer> paramsType = new HashMap<>();
                    Map<String, Autowired> injectConfig = new HashMap<>();
                    injectParamCollector(element, paramsType, injectConfig);

                    if (types.isSubtype(tm, type_Activity)) {
                        // Activity
                        routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                    } else {
                        // Fragment
                        routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                    }
                    routeMeta.setInjectConfig(injectConfig);
                }......
                categories(routeMeta);
            }

然后根据传入参数,所有注释了Route的元素集合routeElement,来创建对应的RouteMeta对象。创建之后调用categories方法对创建的routeMeta进行分组

categories(routeMeta)方法

private void categories(RouteMeta routeMete) {
        if (routeVerify(routeMete)) {
            Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
            if (CollectionUtils.isEmpty(routeMetas)) {
                Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
                    @Override
                    public int compare(RouteMeta r1, RouteMeta r2) {
                        try {
                            return r1.getPath().compareTo(r2.getPath());
                        } catch (NullPointerException npe) {
                            logger.error(npe.getMessage());
                            return 0;
                        }
                    }
                });
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMete);
            }
        }......
    }

categories这个方法会根据每个RouteMeta对象的group来把routeMeta进行分组,拥有相同group的routeMeta被分到一组,存在groupMap中,map结构为<group,set>,也就是说如下图这样的结构:

在这里插入图片描述

回到parseRoutes方法

已经对routeMeta进行了分组,然后回到parseRoutes方法中,遍历groupMap,根据不同的key(group),遍历不同的value(set集合),对集合中的routeMeta依次添加loadInto代码实现,生成java代码:

//遍历Map
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
    String groupName = entry.getKey();
    //构造loadInto方法
    MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
            .addAnnotation(Override.class)
            .addModifiers(PUBLIC)
            .addParameter(groupParamSpec);
    …………
    //获取set
    Set<RouteMeta> groupData = entry.getValue();
    //遍历Set
    for (RouteMeta routeMeta : groupData) {
        …………
        }
        ......
        //根据刚才构造的loadInto,生成IRouteGroup 的loadInto方法体实现
        loadIntoMethodOfGroupBuilder.addStatement(
                "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                routeMeta.getPath(),
                routeMetaCn,
                routeTypeCn,
                className,
                routeMeta.getPath().toLowerCase(),
                routeMeta.getGroup().toLowerCase());
                …………
    }
    //生成ARouter$$Group$$login implements IRouteGroup类和Java文件
    //NAME_OF_GROUP定义在Consts,为"Arouter$$Group$$",groupFileName为类名
    String groupFileName = NAME_OF_GROUP + groupName;
    //PACKAGE_OF_GENERATE_FILE为包名,这里通过poet框架的JavaFile类创建类文件,至此代码文件全部生成完成
    JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
            TypeSpec.classBuilder(groupFileName)
                    .addJavadoc(WARNING_TIPS)
                    .addSuperinterface(ClassName.get(type_IRouteGroup))
                    .addModifiers(PUBLIC)
                    .addMethod(loadIntoMethodOfGroupBuilder.build())
                    .build()
    ).build().writeTo(mFiler);

    //分组名和文件名存入Map,以便后续生成IRouteRoot
    rootMap.put(groupName, groupFileName);
    …………
}

到这里,我们已经生成了ARouter$$Group$$qrcode的Java文件。同样的逻辑会生成ARouter$$Root$$qrcode的Java文件。

这两个文件生成完成,我们看一下ARouter是如何应用这两个类的呢?

路由跳转

第一种路由表加载方式

在ARouter的初始化init方法中,经过不断的调用会到达LogisticsCenter.init方法

LogisticsCenter.init()

if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
    // These class was generated by arouter-compiler.
    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    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 {
    logger.info(TAG, "Load router map from cache.");
    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

在这个方法中,会进行判断:如果是设置了debug模式或者App版本与记录的不同的情况下,扫描所有Dex文件查找所有com.alibaba.android.arouter.routes开头的文件,然后更新到SharePreferences。否则直接从SharePreferences读缓存,减少解析时间。

解析完成之后,按照这些com.alibaba.android.arouter.routes文件下所有com.alibaba.android.arouter.routes.Arouter R o o t 开 头 的 类 , ∗ ∗ 反 射 出 所 有 A R o u t e r Root开头的类,**反射出所有ARouter RootARouterRoot$$xxx类**,实例化,并调用IRouteRoot实现的loadInto方法把存储group和IRouterGroup的map集合赋值给Warehouse.groupsIndex这个Map中,至此这就获得了所有的group和对应的IRouterGroup。

if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
	((IRouteRoot(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} 

至此我们解析了IRouteRoot,那么IRouteGroup什么时候解析?

Postcard类

PostCard是RouteMeta的子类,存储了path、group等信息。

当我们调用ARouter.getInstance().build(“xxxxx”).navigation();这句话时,会调用_ARouter的build方法,根据传入的路径(path),获取出path和group创建出postcard。然后navigation方法会直接调用LogisticsCenter.completion(postcard)方法。

LogisticsCenter.completion(postcard)方法中,会先尝试根据path去取解析过的RouteMeta,但我们目前还没有解析:

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

routeMeta为null,所以我们从刚刚解析得到的Warehouse.groupsIndex集合中通过group去取IRouteGroup类,赋值给iGroupInstance,然后通过这个IRouteGroup类调用loadInto方法,获取到IRouteGroup,进而获取到所有的RouteMeta即拿到了对应的Activity信息,至此我们应用完毕IRouteRoot,从Warehouse.groupsIndex集合中移除它:

Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());

由此可以理解出,相同group下的所有IRouteGroup对应的RouteMeta的注册是在其中一个第一次被请求跳转的时候完成的。Warehouse.routes的结构如下:

在这里插入图片描述

第二种路由表加载方式

就是使用com.alibaba:arouter-register了,它底下使用的是arouter-auto-register这个库,它能够在编译阶段扫描IRouteRoot的实现类,注册到LogisticsCenter。用这种方式能够避免应用开启后再扫描生成路由表的开销。而且能够避免在某些加壳工具加固后无法获取到dex文件中的com.alibaba.android.arouter.routes包下的内容,导致无法生成注册表的问题。

进行intent跳转

以上的两种路由表加载,通过最初的只包含path和group的简单postcard,获取到routeMeta,然后根据获取到的routeMeta完善postCard的信息(postCard是routeMeta的子类),加载结束后,我们拿到了完整的postCard,通过这个postCard我们就可以通过intent去进行跳转,最后跳转会被安排到主线程:

final Intent intent = new Intent(currentContext, postcard.getDestination());
switch (postcard.getType()) {
            case ACTIVITY:
                //创建intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
				//...
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;

总结

在我看来,ARouter的使用及原理主要分3个部分。

第一部分:就是代码的书写,在Application中进行ARouter初始化,在要跳转的目标activity上加Route注解。

第二部分:就是在编译阶段,编译阶段时,ARouter的apt注解管理器会获取所有注解Arouter的元素,根据poet框架来构造出loadInto方法,在根据获得的元素,创建出RouteMeta对象,然后根据group来进行分组,然后对每个routeMeta对象生成2个java文件,IRouteRoot和IRouteGroup的实现类,这两个实现类存放了path和activityClass映射关系的类文件,两个map:<group,IRouterGroup>,<path,RouteMeta>。

第三部分又分为两个小部分:

  • 初始化,调用的ARouter.init(),有一个类叫Warehouse,这个类里面有几个map,对应我们APT生成的类的map存放数据。这个init通过层层调用,最后到达一个LogisticsCenter.init()方法,在这里会获取到所有配置route的文件,然后通过反射拿到IRouterRoot的实现类,拿到了group和IRouterGroup,将这写入Warehouse的某个map当中。
  • 跳转阶段:调用ARouter.getInstance().build("/login/login1").navigation(),这个会调用-Route.build方法,根据传入path,构造出一个postCard(存放了group和path,是RouteMeta的子类),然后通过postCard去获取IRouteGroup类即间接获取RouteMeta,有缓存则直接拿,没有缓存则从Warehouse的map中去拿,拿到后反过来完善postCard的信息,然后调用navigation时,实际上调用的是_ARouter的-navigation方法,这里面自动new一个intent,把postCard中存的activity传入,然后调用runInMainThread回到主线程跳转。(runInMainThread是通过得到主线程的Looper,再判断当前线程是不是主线程,如果不是就通过handler发消息,如果是就直接run。)
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值