@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 ARoutertest 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 ARouterapp implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("test", ARoutertest.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方法把类文件的内容加载到索引中。
通过插件加载路由表
如果想缩短ARouter初始化的时间,可以用ARouter的Gradle插件,这个插件能自动加载路由表,这样ARouter初始化的时候就不需要读取类的信息,从而缩短初始化时间。
插件的工作原理就是在代码编译的时候,插件会找到LogisticsCenter类的loadRouterMap方法,然后在方法中插入路由相关的代码,这样初始化的时候就不会从dex文件中扫描路由表了。
路由表的跳转
使用ARouter.getInstance().build(“/test/activity”).navigation()发起路由操作,进行跳转,会调用_ARouter的navigation()方法。
_ARouter的navigation()方法有两种重载。一种是用于创建服务service,一种是用于路由跳转。
protected 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; }
// Set application to postcard. postcard.setContext(mContext);
LogisticsCenter.completion(postcard); return (T) postcard.getProvider(); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); return null; } }
创建服务service流程相对比较简单,通过LogisticsCenter.buildProvider创建一张Postcard明信片,包含最基本的group和path信息,然后给Postcard设置Context,最后通过LogisticsCenter.completion获取路由元信息并填充至Postcard中。
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { //预处理服务 PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; }
// Set context to postcard. postcard.setContext(null == context ? mContext : context);
try { //完善Postcard LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { //完善失败降级策略 ...... } ...... if (!postcard.isGreenChannel()) { // 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(postcard, requestCode, callback); }
/**
- Interrupt process, pipeline will be destory when this method called.
- @param exception Reson of interrupt. */ @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(postcard, requestCode, callback); }
return null; }
用于路由跳转流程相对复杂一点,大致有这么几步:
1、预处理服务
2、完善Postcard
3、完善失败降级策略
4、拦截器链路
5、按类型跳转
预处理服务
预处理服务可以让我们在ARouter进行跳转前,根据PostCard的内容判断是否要独立地对这次跳转进行处理,是的话则在onPretreatment()中返回false即可。
实现预处理服务只需要实现PretreatmentService接口,并加上一个path内容任意的@Route注解即可。
如果实现了预处理服务,并且onPretreatment()中返回false,则中断跳转流程,不继续往下处理。
完善Postcard
调用完预处理服务后,_ARouter就会用LogisticsCenter来加载路由表,路由表也就是RouteProcessor生成的路由文件。
在_ARouter初始化时,会把LogisticsCenter也进行初始化,而LogisticsCenter的初始化方法中,会读取RouteProcessor创建好的路由表,然后放到对应的索引 index中。
有了索引,当_ARouter调用LogisticsCenter的completion()方法时,就可以用索引从Warehouse的routes 中获取路由元信息。
public synchronized static void completion(Postcard postcard) { ...... RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) { ...... } else { postcard.setDestination(routeMeta.getDestination()); postcard.setType(routeMeta.getType()); postcard.setPriority(routeMeta.getPriority()); postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri(); if (null != rawUri) { // Try to set params into bundle. 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()); }
switch (routeMeta.getType()) { case PROVIDER: // if the route is provider, should find its instance // Its provider, so it must implement 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) { logger.error(TAG, "Init provider failed!", e); throw new HandlerException("Init provider failed!"); } } postcard.setProvider(instance); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } }
首先如果LogisticsCenter根据索引查找不到对应的RouteMeta,那就说明routes还没有被填充,此时LogisticsCenter就会获取group的RouteMeta,然后把group下的路径填充到routes中,然后再调用一次completion() ,这时就可以取填充Postcard的信息了。
有了routeMeta之后,从routeMeta中取值,设置到postcard属性中,解析Uri中的参数,设置到postcard属性中,用于后续跳转时设置到Bundle里。
填充完了 Postcard 的信息后,LogisticsCenter 会根据 Postcard 的类型来做不同的操作,并且当类型是PROVIDER和FRAGMENT的的时候,设置postcard的greenChannel,其实就是说Provider和Fragment是会跳过拦截器链路的。
完善失败降级策略
当ARouter在完善Postcard信息的过程中遇到了异常,如果在navigation时有传入NavigationCallback,则针对此次跳转失败,回调onLost,如果没有callback,将使用全局的降级策略:
if (null != callback) { callback.onLost(postcard); } else { // No callback for this invoke, then we use the global degrade service. DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null != degradeService) { degradeService.onLost(context, postcard); } }
同预处理服务类似,实现降级策略只需要实现DegradeService接口,并加上一个path内容任意的@Route注解即可。
拦截器链路
当不是greenChannel时,将进入拦截器链路,否则直接进入按类型跳转。在上面完善Postcard时,Provider和Fragment设置了greenChannel,是会跳过拦截器链路的。当然也可以在navigation()方法前调用greenChannel()方法,让该次跳转跳过拦截器链路。
拦截器可以用来在跳转过程中处理事件,比如做登陆检查,拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行。
实现拦截器只需要实现IInterceptor接口,使用注解@Interceptor(priority = 8, name = “xxxx”),priority为该拦截器优先级,多个拦截器会按优先级顺序依次执行。
在process方法中需要调用onContinue()或onInterrupt()方法,至少需要调用其中一种方法,否则不会继续路由流程。onContinue()表示处理完成,交换控制权给 ARouter,onInterrupt()表示出现异常需要中断路由流程。
按类型跳转
当处理完拦截器后,navigation()中就会调用_navigation()方法,这也是具体进行跳转的方法。在这个方法中,会根据Postcard的路由类型RouteType来判断,按类型进行跳转。
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) { final Context currentContext = postcard.getContext();
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);
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新