背景
组件化可以用来解决项目中的一些痛点
- 编译耗时
- 业务复杂互相依赖
- 模块代码依赖复杂,不利于开发协调
阿里的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注解处理器中生成的文件
ARouter$$Group$$xxx(可能有多个)ARouter$$Providers$$xxx(只有一个)ARouter$$Root$$xxx(只有一个)

然后我们可以看看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不能设置相同优先级的拦截器,否则会抛出异常。
参考文章