ARouter源码分析

417 阅读3分钟

背景

组件化可以用来解决项目中的一些痛点

  1. 编译耗时
  2. 业务复杂互相依赖
  3. 模块代码依赖复杂,不利于开发协调

阿里的ARouter就是一个非常优秀的组件化框架。

源码分析是基于最新版本1.5.0

APT,AutoService和JavaPoet

APT(Annotation Procdessing Tool) 注解处理工具,它在编译器对指定的注解进行解析,然后做一些其他处理(如通过javapoet生成新的文件)

自定义Processor继承自AbstractProcessor,然后实现process方法,在编译期会执行Processor的process方法,然后我们就可以在这个方法处理注解了。我们需要指明它需要处理什么注解,使用google开源的自动注册工具AutoService

implementation 'com.google.auto.service:auto-service:1.0-rc2'

通过该工具添加来为RouteProcessor设置它需要的配置,比如

@AutoService(Processor.class)
//指定了需要处理的注解的路径地址,在此就是XXX.class的路径地址。
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
  
//可以看到这个两个路径地址为
public static final String ANNOTATION_TYPE_ROUTE = FACADE_PACKAGE + ".annotation.Route";
public static final String ANNOTATION_TYPE_AUTOWIRED = FACADE_PACKAGE +".annotation.Autowired";

我们可以在arouter-annotation找到这两个路径的地址,也就是我们的Route和Autowired注解,也就是RouteProcessor处理器只处理这两个注解。

同理我们可以看到InterceptorProcessor处理Interceptor注解,AutowiredProcessor处理Autowired注解。

在使用ARouter注解的时候,按照官方文档是需要在每个module里面的的build.gradle中配置如下信息:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

通过查看BaseProcessor源码可以看到,KEY_MODULE_NAME在这里添加,所有的Processor统一继承BaseProcessor

//BaseProcessor
@Override
public Set<String> getSupportedOptions() {
    return new HashSet<String>() {{
        this.add(KEY_MODULE_NAME);
        this.add(KEY_GENERATE_DOC_NAME);
    }};
}
//Consts.class
public static final String KEY_MODULE_NAME = "AROUTER_MODULE_NAME";

可以看到在Processor的初始化方法中看到获取moduleName和做一些初始化配置

1.BaseProcessor的init()
@Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
				//文件管理器
        mFiler = processingEnv.getFiler();
        //获取类型处理工具
        types = processingEnv.getTypeUtils();
				//获取日志信息工具类
        elementUtils = processingEnv.getElementUtils();
        typeUtils = new TypeUtils(types, elementUtils);
        logger = new Logger(processingEnv.getMessager());

        // Attempt to get user configuration [moduleName]
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
          	//获取用户配置的moduleName
            moduleName = options.get(KEY_MODULE_NAME);
            generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
        }

        if (StringUtils.isNotEmpty(moduleName)) {
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");

            logger.info("The user has configuration the module name, it was [" + moduleName + "]");
        } else {
            logger.error(NO_MODULE_NAME_TIPS);
            throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
        }
    }
2.RouteProcessor的process()

在RouteProcessor的process()中通过parseRoutes去处理注解,然后使用JavaPoet去生成代码

implementation 'com.squareup:javapoet:1.7.0'

代码如下

//process()

//首先获取所有的Route注解
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
//再去处理这些注解
this.parseRoutes(routeElements);

//parseRoutes()
//通过JavaPoet去生成代码,具体API方法就不介绍了
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
  ···
     					// Generate groups
    					//生成Group记录分组表
                String groupFileName = NAME_OF_GROUP + groupName;
                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);

                logger.info(">>> Generated group: " + groupName + "<<<");
                rootMap.put(groupName, groupFileName);
                docSource.put(groupName, routeDocList);
  					···
            ···  
            // Write provider into disk
            String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(providerMapFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IProviderGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfProviderBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");
					
            // Write root meta into disk.
  				  //生成Root类 作用:记录<分组,对应的Group类>
            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);

            logger.info(">>> Generated root, name is " + rootFileName + " <<<");
}

在RouteProcessor注解处理器中生成的文件

  1. ARouter$$Group$$xxx (可能有多个)
  2. ARouter$$Providers$$xxx (只有一个)
  3. ARouter$$Root$$xxx (只有一个)

![image-20200807104632500](/Users/jackie/Library/Application Support/typora-user-images/image-20200807104632500.png)

然后我们可以看看ARouter$$Group$$yb中的代码

//ARouter$$Group$$yb
public class ARouter$$Group$$yb implements IRouteGroup {
          @Override
          public void loadInto(Map<String, RouteMeta> atlas) {
              atlas.put("/yb/account", RouteMeta.build(RouteType.ACTIVITY, AccountActivity.class, "/yb/account", "yb", null, -1, -2147483648));
              atlas.put("/yb/address/add", RouteMeta.build(RouteType.ACTIVITY, 
              ···
    			}
//因为使用了不同的路径开头,所以也生成了`ARouter$$Group$$photo`文件   
//代码中添加了路径 const val PATH_PHOTO_VIEW = "/photo/view",也可以说是不同的模块路径
//ARouter$$Group$$photo                                                        
public class ARouter$$Group$$photo implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/photo/view", RouteMeta.build(RouteType.ACTIVITY, PhotoViewActivity.class, "/photo/view", "photo", null, -1, -2147483648));
  }
}                                                           

可以看到这里面每个路径对应一个具体的Activity,比如AccoutActivity对应于"/yb/account"

const val PATH_GROUP = "yb"
const val PATH_ACCOUNT = "/$PATH_GROUP/account"
//添加路径
@Route(path = PATH_ACCOUNT)
class AccountActivity: YBBaseActivity() {

然后看一下生成ARouter$$Root$$app中的代码,也就是我们两个模块不同路径的分组

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

通过上面的讲解,我们可以理解保存了两个Map,一个保存Group列表,一个保存Group下面的路由地址和Activity/Fragment class的关系,也就是这两者的映射关系。通过map.get("router address")拿到Activity,通过调用startActivity()就可以了,也就是我们平时写的调用代码

ARouter.getInstance().build(PATH_ACCOUNT).navigation()

这里先简单看一下源码的调用

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
        		//关键方法 postcard.getDestination(),获取到我们的Activity
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            // Set flags.
						···
            // Set Actions
						···
            // Navigation in main looper.
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });
            break;        

通过postcard.getDestination(),获取到我们的Activity,然后我们看看setDestination,这个的destination就是目标Activity

 public RouteMeta setDestination(Class<?> destination) {
        this.destination = destination;
        return this;
 }

该方法是在LogisticCenter的completion(Postcard postcard)中被调用的

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }
		//关键方法,进行赋值,一开始肯定是没有值的,因为还没赋值进去,只是做了简单初始化
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
        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 {
                if (ARouter.debuggable()) {
                    logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                }
              	//在这里Warehouse.routes才进行第一次赋值
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);
                Warehouse.groupsIndex.remove(postcard.getGroup());

                if (ARouter.debuggable()) {
                    logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                }
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }

            completion(postcard);   // Reload
        }
    } else {
        //此处调用
        postcard.setDestination(routeMeta.getDestination());
        ···
    }
}

我们的destination是从routeMeta中获取的,而routeMeta则是从Warehouse.routes中获取的,而Warehouse.routes是从groupMeta中获取的,而groupMeta是从Warehouse.groupsIndex.get(postcard.getGroup())中获取的,下面来看看Warehouse.groupsIndex是在哪里初始化的

private static void registerRouteRoot(IRouteRoot routeRoot) {
    markRegisteredByPlugin();
    if (routeRoot != null) {
        routeRoot.loadInto(Warehouse.groupsIndex);
    }
}
//最终在register中调用
private static void register(String className) {
        if (!TextUtils.isEmpty(className)) {
            try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof IRouteRoot) {
                    registerRouteRoot((IRouteRoot) obj);
                ····
            } catch (Exception e) {
                logger.error(TAG,"register class error:" + className);
            }
        }
    }

但是我们找不到这个register方法的调用,那到底是在哪里被调用了呢,一般这个时候我们可以看看这个库有没有使用什么gradle插件在Transform中插入代码,果然可以看到ARouter下面有个arouter-gradle-plugin插件,找到RegisterTransform中使用RegisterCodeGenerator去生成代码,代码中使用了ASM进行注入代码

@Override
void visitInsn(int opcode) {
    //generate code before return
    if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
        extension.classList.each { name ->
            name = name.replaceAll("/", ".")
            mv.visitLdcInsn(name)//类名
            // generate invoke register method into LogisticsCenter.loadRouterMap()
            mv.visitMethodInsn(Opcodes.INVOKESTATIC
                    , ScanSetting.GENERATE_TO_CLASS_NAME
                    , ScanSetting.REGISTER_METHOD_NAME
                    , "(Ljava/lang/String;)V"
                    , false)
        }
    }
    super.visitInsn(opcode)
}

static final String GENERATE_TO_CLASS_NAME = 'com/alibaba/android/arouter/core/LogisticsCenter'
static final String REGISTER_METHOD_NAME = 'register'

可以看到是在LogisticsCenter.loadRouterMap()中注入了register方法,然后我们可以看看loadRouterMap()

private static void loadRouterMap() {
    registerByPlugin = false;
    //auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    // registerRouteRoot(new ARouter..Root..modulejava());
    // registerRouteRoot(new ARouter..Root..modulekotlin());
}

代码中都添加了注释说明会用到gradle插件去添加register方法,而loadRouterMap在初始化方法init中会被调用

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        long startInit = System.currentTimeMillis();
        //billy.qi modified at 2017-12-06
        //load by plugin first
        loadRouterMap();

在分析代码的时候有时候并不用从init方法一步步分析,因为你可能找不到里面具体哪个是真正的入口,如果从最终的调用一步步往上推,反而能很快找到清晰的调用路线,从而更快的理清整个源码。

现在我们可以知道,通过在Processor的process方法中进行对注解的处理,通过JavaPoet生成代码,通过gradle插件使用Transform去动态插入代码。然后用户手动调用init去进行初始化。

APT和Transform的处理时间

注解的处理时机是在app:compileDebugJavaWithJavac这个task当中,而自定义的transform的处理时间是在class转化为dex的时候,所以注解的处理肯定是先于Transform处理的。所以通过注解后生成的文件我们才能用register去注册,所以在Transform中进行动态的代码注册,否则一开始就注册是获取不到对应的映射关系的。

ARouter的init()的一些细节

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        long startInit = System.currentTimeMillis();
        //billy.qi modified at 2017-12-06
        //load by plugin first
        loadRouterMap();
        //用户使用自动注册插件
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else { //不使用自动注册插件,手动去获取映射表
            Set<String> routerMap;

            // It will rebuild router map every times when debuggable.
            //如果是debug模式或者有新版本的时候,
            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.
                // 从apk的dex文件中去获取生成的(com.alibaba.android.arouter.routes下面的)文件
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
              //如果routerMap不是空,从SharedPreference把值在放进AROUTER_SP_KEY_MAP 
              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 {
                //直接从SharePreference中去获取routerMap
                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>()));
            }

            logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
            startInit = System.currentTimeMillis();
						//然后通过for循环把生成的路径和对应的Activity/Fragment全都放到Warehouse.groupsIndex
            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);
                }
            }
        }
		···
        }
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

可以看到,如果我们不使用gradle插件进行自动注册,如果是debug模式或者有新版本的时候,从apk的dex(for循环遍历一个dex,支持MultiDex)文件中去获取生成的(com.alibaba.android.arouter.routes下面的)文件,并存储到Sharepreference中,然后对Warehouse下面的一个个Map或者list进行赋值,Warehouse就类似一个仓库,存储了所有路由相关的信息。else情况下则直接从SharedPreference中获取。

支持InstantRun

在debug模式下,通过tryLoadInstantRunDexFile方法查找文件,多看源码真的能发现很多好东西。

if (ARouter.debuggable()) { // Search instant run support only debuggable
    sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}
LogisticsCenter.completion(postcard)

Warehouse.routes会在LogisticsCenter.completion(postcard)中被初始化赋值。在 init 方法里,只是初始化了所有组的 class 信息,但组所在 class 中每一个元素是没有被初始化的。因此这里如果直接通过 path 去查找目标类是找不到的,因此可以理解为,每个组中第一次被使用的路由,肩负着整个组所有路由信息被加载到 WareHouse 仓库的重任,这个组的信息所有信息会被加载到 WareHouse.routes 这个集合中(准确来说是一个 map,path 为 key,RouteMate 为值的一个 HashMap)。同时会从 WareHouse.groupsIndex 中移除当前组,毕竟这一项已经没用了。然后再次递归调用一下自己,这一次就可以直接从 WareHouse.routes 这个集合中直接找到 path 对应的 RouteMate 了。如果再找不到,那就是什么地方出问题了,就报错了。

在初始化时只加载组信息的做法,可以说是非常巧妙,算的上是一种分治的思想。将所有路由 RouteMate 分组,减少初始化的压力,然后用到某个组的时候,才去初始化整个组内所有的 RouteMate 。这样,虽然可能会影响组内第一个使用的路由,但是总体来说还是值得的,毕竟减少了初始化的压力。甚至大部分情况下,用户使用时,常规的跳转可能也就那么几个,完全没有必要在初始化是加载所有的信息。当然,这种问题见仁见智,也许全部初始化也是一个不错的策略。

拦截器InterceptorProcessor

有了上面的那些分析,我们直接来看生成的文件ARouter$$Interceptors$$app,那些怎么生成的就不用分析了,都是类似的过程

public class ARouter$$Interceptors$$app implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(1, LoginARouterInterceptor.class);
  }
}

InterceptorProcessor不能设置相同优先级的拦截器,否则会抛出异常。

参考文章

rebooters.github.io/2019/07/20/…

blog.csdn.net/crazy1235/a…