ARouter源码分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARouter源码分析相关的知识,希望对你有一定的参考价值。

参考技术A

Annotation Processing Tool,自定义注解处理器。
android的基本上都知道这个吧。许多第三方库都使用了APT去实现自己的功能,比如butterknife,比如X2C,比如我们要讲的ARouter。
其基本做法是:

一般是自定义gradle Transform + ASM,实现AOP,可以在编译期修改project和第三方依赖库中的class文件(比如ARouter源码中的arouter-gradle-plugin模块),与APT主要是生成.java文件不同,ASM操作的是.class文件。
自定义gradle Transform功能很强大,可以与ASM结合,修改.class,也可以操作资源文件(比如统一压缩图片,转png大图为webp等)。
至于ASM,基于修改.class文件,我们即可以用ASM来插桩统计方法耗时,也可以用来实现自动化埋点,甚至是修改第三方lib中的crash...

使用方法可以看 ARouter 。
带着问题看源码,这里主要的问题是:

ARouter的核心方法。

这个方法算是核心中的核心了。
其实也只做了两件事情

总的来说,只干了一件事情,扫描所有的dex文件,找到com.alibaba.android.arouter.routes包下的所有文件并返回。这里的操作都是耗时操作。
但是com.alibaba.android.arouter.routes包下都是什么文件呢?
比如:

比如:

比如:

比如:

这些都是APT生成的辅助类。

①项目编译期,通过APT,生成辅助类(所有的辅助类包名都是com.alibaba.android.arouter.routes)
包括

②ARouter#init初始化的时候,扫描dex文件,找到①生成的辅助类文件(也即是包com.alibaba.android.arouter.routes下的文件),放到routerMap中

注意,这里没有实例化IRouteGroup,IRouteGroup的信息都在IRouteRoot中,这样做的目的是为了实现分组route的加载,用到了哪个group的route的信息,才会加载这个group的信息,没用到就不加载。这里可以仔细想想IProviderGroup/IInterceptorGroup/IRouteGroup的区别。
该方法执行完了以后,

至此,就完成了初始化路由表的操作。
我们回过头来瞄一眼ARouter#init,里面初始化路由表以后,执行了_ARouter#afterInit

这一句看着很熟悉。

页面路由跳转/IProvider/拦截器都是ARouter.getInstance().build("/test/activity").navigation()这种形式的话,我们就先从拦截器interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();开始分析吧。

虽然这里我们是想看拦截器的实现,但是要明确一点:InterceptorServiceImpl是IProvider的实现类,获取InterceptorService也就是获取一个IProvider。有一点绕,简单来说,ARouter使用一个IProvider来实现拦截器的初始化。
后面的逻辑就变成了获取一个IProvider上了。

也即是ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法中,ARouter.getInstance().build("/arouter/service/interceptor")做的事情就是创建一个Postcard,其path是"/arouter/service/interceptor",group是"arouter".

简单来说,这里做的事情有

注意addRouteGroupDynamic(postcard.getGroup(), null)这个方法,通过groupName去groupIndex中查找,那"arouter"对应的是谁呢?正是ARouter$$Group$$arouter.class。
反射创建ARouter$$Group$$arouter对象,并执行ARouter$$Group$$arouter#loadInto方法

现在我们回过头来继续看LogisticsCenter#completion

总结一下LogisticsCenter#completion方法做了啥:

以ARouter.getInstance().build("/arouter/service/interceptor").navigation()举例说明就是:

init方法做的事情很单一,就是一次性实例化全部的拦截器,存到 Warehouse.interceptors中。(想想为什么要这么做?)
这样,ARouter.getInstance().build("/arouter/service/interceptor").navigation()就分析完了,ARouter#init的时候,会创建所有的拦截器实例。ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法返回的是InterceptorServiceImpl的实例。

另外,_ARouter#navigation(Context, Postcard, int, NavigationCallback)方法的最后,调用了_ARouter#_navigation

方法看着长,内容却很简单:

这样, ARouter#init 就分析完了, 总结 一下:

另外使用ARouter.getInstance().build("path").navigation()方法 获取IProvider的流程 如下:

Activity跳转的流程如下:

至此,我们回答了问题1/问题2和问题4.
下面我们来看下剩下的问题
问题3:拦截器是如何生效的?
我们可以看看_ARouter

执行_ARouter#navigation的时候,执行了interceptorService.doInterceptions方法,前面我们已经知道,执行了interceptorService实际上是InterceptorServiceImpl。

这里一个一个调用拦截器,如果有拦截器拦截,就中断调用,否则,调用下一个拦截器进行拦截。

所以, 拦截器 总结如下

最后,我们来看下最后一个问题。
问题5 ARouter的Gradle Plugin做了哪些优化?
该问题的关键是LogisticsCenter#loadRouterMap

上面的是gradle plugin修改之前的,下面的loadRouterMap是gradle plugin修改之后的。

瞄一眼register方法我们就能明白,这还是之前的那一套,跟不使用gradle plugin不同的地方在于,这里不需要扫描dex去找IRouteRoot/IInterceptorGroup/IProviderGroup,在编译期,gradle plugin就已经找到了这些,然后生成新的loadRouterMap方法。

ARouter源码分析—— 缓存与优化

Arouter源码分析系列建议从最初开始阅读,全部文章请访问https://github.com/AlexMahao/ARouter

本篇博客意在记录ARouter中的一些优秀策略。

辅助类加载机制

ARouter在实现基本功能时,使用apt在指定包名下生成了一些辅助类。辅助类的查询逻辑如下。

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.
                    // 获取com.alibaba.android.arouter.routes的类列表,及Processor生成的辅助类
                    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或者是否是新版本,其中一个成立,则重新查询并且遍历一下指定包下的所有辅助类。并且将辅助类的全路径保存到sp中。

如果不满足条件,则从sp中取。

尽可能减少耗时操作调用的次数。

路由的懒加载

对于一个大型app,存在的路由地址数量很大。而ARouter的初始化方法都是在Application中,那么势必导致app加载时间过长。ARouter通过分组懒加载的形式进行加载。

ARouter在初始化的时候,只是加载了分组的类,即路由清单的class,而没有加载详细的路由清单。

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

然后当有路由地址跳转的时候,判断是否能查寻对应地址信息,如果没有,则会根据路由地址确定其分组,然后加载该组的路由清单。

 public synchronized static void completion(Postcard postcard) 
        if (null == postcard) 
            throw new NoRouteFoundException(TAG + "No postcard!");
        
        // 获取路由地址对应的信息
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        // 如果为null可能是由于对应组别的路由清单没有加载
        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()));
                    
                    // 加载分组的路由清单
                    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
            
         
   

以上是关于ARouter源码分析的主要内容,如果未能解决你的问题,请参考以下文章

ARouter源码分析—— 拦截器源码分析

ARouter源码分析—— 缓存与优化

ARouter 源码分析

ARouter 源码分析

ARouter源码分析—— 路由跳转分析

Arouter 源码学习 1