ARouter基本使用及原理分析_arouter跨模块调用 init provider failed!

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

作者:愿天深海

ARouter简介

ARouter是阿里开源的一款帮助Android App进行组件化改造的路由框架,是Android平台中对页面和服务提供路由功能的中间件,可以实现在不同模块的Activity之间跳转。

ARouter的特点是灵活性强还能帮助项目解耦。

除了广为人知的Activity跳转之外,ARouter还支持获取Fragment,解耦服务使得跨模块API调用等等

ARouter原理概述

ARouter使用@Route注解,在编译时期通过APT技术生成类文件用于存储path和activityClass的映射关系。

在app进程启动的时候会拿到这些类文件,把里面存储的映射关系数据读到内存里,保存在路由表map中。 在进行路由跳转时,通过ARouter的build()方法传入要到达页面的路由地址,ARouter在路由表中找到路由地址对应的activityClass,然后new Intent(),通过ARouter的withString()方法传入携带参数,内部调用intent.putExtra(),通过ARouter的navigation()跳转,内部调用startActivity(intent)。

这样就实现了在不同模块之间,不相互依赖,却顺利启动对方的Activity。

ARouter基本用法

添加依赖与配置

//注解处理插件
plugins {

id ‘kotlin-kapt’
}

//注解处理选项
kapt {
arguments {
arg(“AROUTER_MODULE_NAME”, project.getName())
}
}

//依赖
dependencies {
// 替换成最新版本, 需要注意的是api要与compiler匹配使用,均使用最新版可以保证兼容
implementation ‘com.alibaba:arouter-api:x.x.x’
kapt ‘com.alibaba:arouter-compiler:x.x.x’

}

添加注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = “/test/activity”)
public class YourActivity extend Activity {

}

初始化SDK

if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

发起路由操作

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

// 2. 跳转并携带参数
ARouter.getInstance().build(“/test/1”)
.withLong(“key1”, 666L)
.withString(“key3”, “888”)
.withObject(“key4”, new Test(“Jack”, “Rose”))
.navigation();

ARouter架构概览

ARouter项目代码结构如上,红框内为最核心的4部分,架构关系如下,

ARouter项目中包含了API、编译器Compiler、插件Gradle Plugin和注解Annotation4 个模块。

API

API 模块由launcher、core、exception、thread、facede、utils和base子模块组成。

  • launcher:包含了启动器 ARouter。
  • core:包含物流中心 LogsticsCenter 和仓库 Warehouse 等类。
  • exception:包含了一些异常类。
  • thread:包含了CancellableCountDownLatch,ARouter 的拦截器链是放在子线程中执行的,就用到了它。
  • facede:包含了导航回调 NavigationCallback 和 拦截器IInterceptor 等接口。
  • utils:包含了 ARouter 自定义的日志打印器等工具类。
  • base:只有一个用于保存拦截器的 UnitqueKeyTreeMap。
Compiler

Compiler 模块用于生成路由表,@Autowired、@Interceptor 和 @Route 注解对应的注解处理器分别是 AutowiredProcessor、InterceptorProcessor 以及 RouteProcessor ,都在 Compiler 中。

Register Plugin

Register Plugin 模块包含了注册代码生成器 RegisterCodeGenerator 和 RegisterTransform,如果使用了ARouter的路由表加载插件,那这个路由表就会由Register插件加载。

Annotaion

Annotaion模块比较简单,只包含了一些注解和枚举类。

APT原理

ARouter的使用非常方便,得益于APT技术。APT的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。

ARouter为了方便实现注解处理器还额外用了两个库。

  • JavaPoet,提供了调用对象方法的方式生成需要的代码,而不再需要人为地用StringBuilder去拼接代码,再使用IO写入文件。
  • Auto-Service,提供了简便的方式去注册APT,避免了原本繁琐的注册步骤。
@Route

@Route的定义:

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

/**

  • Path of route
    */
    String path();

    }

  • @Target({ElementType.TYPE}):表示这个注解是修饰类的

  • @Retention(RetentionPolicy.CLASS):表示需要保留到编译时

使用该注解时有一个主要的参数path:

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = “/test/activity”)
public class YourActivity extend Activity {

}

这样编译时能获取到@Route所注解的类,并且能获取到path路径。

RouteProcessor

RouteProcessor是@Route注解对应的注解处理器。

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor

  • Auto-Service这个库为Processor完成了自动注册.
  • @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED}):表明了当前Processor是处理哪些注释的。

RouteProcessor继承于BaseProcessor,在init方法中获取到了每个模块的moduleName。

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

RouteProcessor的process方法是对注解处理的地方,它直接获取了所有使用@Route注解的元素。

Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);

拿到使用注解的元素后就会进入this.parseRoutes(routeElements)方法。这个方法使用JavaPoet生成Java文件。如果不用这个库也可以使用StringBuilder去写Java文件的内容。

IRouteGroup

先来看一下RouteProcessor生成的产物,在下图路径下可以看到ARouter的生成产物

public class ARouter G r o u p Group Grouptest implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put(“/test/activity”, RouteMeta.build(RouteType.ACTIVITY, YourActivity.class, “/test/activity”, “test”, null, -1, -2147483648));
}
}

RouteMeta是包含了@Route所注解的元素的必要信息,最明显的就是YourActivity.class,有了它,我们就可以通过Intent跳转到这个Activity了。

ARouter$$Group$$test这个类继承自IRouteGroup ,实现了接口中的loadInto方法。

loadInto方法逻辑很简单,传入一个map,将注解的path值作为key,将元素(RouteMeta)作为value作为Value放入map。如果完成了这个方法,就完成了Activity的注册。

IRouteRoot

public class ARouter R o o t Root Rootapp implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put(“test”, ARouter G r o u p Group Grouptest.class);
}
}

ARouter$$Root$$app实现了IRouteRoot接口,内容非常相似。通过loadInto方法,往Map中插入以group名为Key,IRouteGroup实现类为Value的内容。

group默认就是path中第一个斜杠之后的内容(@Route(path=“/group/xxx”))

如果调用了这个方法,那么可以通过group拿到IRouteGroup实现类的class,有了class实例化之后就能通过前面所说的拿到Activity.class了。

整体的结构如下图所示:

RouteProcessor.process()

回过头来继续看RouteProcessor的process方法

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

} catch (Exception e) {
logger.error(e);
}
return true;
}

return false;
}

获取了所有使用@Route注解的元素,将它们放进了parseRoutes方法用于生成IRouteGroup和IRouteRoot。这里面使用JavaPoet提供的类,通过方法调用的形式生成代码。

路由表的生成

RouteProcessor的process方法对于声明了 @Route 注解的类的处理,大概分为4个步骤: 1、获取路由元素
2、创建路由元信息
3、把路由元信息进行分组
4、生成路由文件

在上面的分析中,通过roundEnv.getElementsAnnotatedWith(),已经获取了所有使用@Route注解的元素,然后将它们放进了parseRoutes方法。

这里说的路由元信息指的是 RouteMeta,RouteProcessor 会把声明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一个 RouteMeta 关联起来。

for (Element element : routeElements) {

// Activity or Fragment
if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
// Get all fields annotation by @Autowired
Map<String, Integer> paramsType = new HashMap<>();
Map<String, Autowired> injectConfig = new HashMap<>();
injectParamCollector(element, paramsType, injectConfig);

if (types.isSubtype(tm, type_Activity)) {
// Activity
logger.info(“>>> Found activity route: " + tm.toString() + " <<<”);
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
// Fragment
logger.info(“>>> Found fragment route: " + tm.toString() + " <<<”);
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}

routeMeta.setInjectConfig(injectConfig);
}

categories(routeMeta);
}

在这段代码中,完成了RouteMeta类的构建,还获取了Activity通过@AutoWired注解的接收参数。然后通过categories(routeMeta)方法,对所有RouteMeta进行分组。

为什么要分组呢?随着项目的迭代,组件数量会越来越多,将这么多的组件信息都放到一个map里,显然会对内存造成很大的问题,且加载耗时也会随之增加。Arouter采用的方法就是“分组+按需加载”,同时,分组也利于管理。

private void categories(RouteMeta routeMete) {
if (routeVerify(routeMete)) {
logger.info(">>> Start categories, group = " + routeMete.getGroup() + “, path = " + routeMete.getPath() + " <<<”);
Set routeMetas = groupMap.get(routeMete.getGroup());
if (CollectionUtils.isEmpty(routeMetas)) {

routeMetaSet.add(routeMete);
groupMap.put(routeMete.getGroup(), routeMetaSet);
} else {
routeMetas.add(routeMete);
}
}

}

在 RouteProcessor 中有一个 groupMap,在 RouteMeta 创建好后,RouteProcessor 会根据其group作为Key进行分组,放入到 groupMap 中。RouteMeta本身会放入一个Set,Set中所有RouteMeta的group都是相同的,作为Map的Value。

当 RouteProcessor 把 RouteMeta 分组好后,就会用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由这些文件组成的,JavaPoet 是 Square 开源的代码生成框架。

// Write root meta into disk.
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(mFiler);

生成的路由文件就是前面所看到RouteProcessor的产物,就是下面这些:

路由表的加载

加载路由表,其实就是加载RouteProcessor所生成的类文件。

在调用ARouter的init()初始化方法时,ARouter会调用LogisticsCenter的init()方法,在该方法中,会loadRouterMap()优先通过插件加载路由表,然后判断当前路由表加载方式是否为插件,不是的话则从Dex中加载路由表。

/**

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

    try {
    long startInit = System.currentTimeMillis();
    //load by plugin first
    loadRouterMap();
    if (registerByPlugin) {
    //通过插件加载路由表
    logger.info(TAG, “Load router map by arouter-auto-register plugin.”);
    } else {
    //从Dex中加载路由表

    }

    } catch (Exception e) {
    throw new HandlerException(TAG + “ARouter init logistics center exception! [” + e.getMessage() + “]”);
    }
    }
从Dex中加载路由表

通过Dex加载路由表的流程大致如上图,接下去来一点一点看一下LogisticsCenter的init()方法中从Dex中加载路由表部分:

// It will rebuild router map every times when debuggable.
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()));
}

如果是设置了debug模式或者是新版本的情况下,扫描所有Dex文件查找所有com.alibaba.android.arouter.routes开头的文件,然后更新到SharePreferences。否则直接从SharePreferences读缓存,减少解析时间。

这里使用了ClassUtils读取Dex文件,并从Dex文件中读取路由表。

for (String className : routerMap) {
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);
}
}

把路由表保存到 SharedPreferences 后,就会根据类名的后缀判断类是 IRouteRoot 、IInterceptorGroup 还是 IProviderGroup ,然后实例化成不同的对象并调用loadInto方法把类文件的内容加载到索引中。

通过插件加载路由表

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

assName).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}

把路由表保存到 SharedPreferences 后,就会根据类名的后缀判断类是 IRouteRoot 、IInterceptorGroup 还是 IProviderGroup ,然后实例化成不同的对象并调用loadInto方法把类文件的内容加载到索引中。

通过插件加载路由表

[外链图片转存中…(img-CLr5eeVQ-1715805205828)]
[外链图片转存中…(img-0I5zWMOO-1715805205829)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值