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

Posted Alex_MaHao

tags:

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

相关工程githubhttps://github.com/AlexMahao/ARouter

逻辑分析

Arouter的路由跳转整体可分为三个步骤:

  • 编译时期利用Processor生成路由清单文件。
  • 运行时期加载路由清单文件。
  • 跳转时期根据标识符查询路由清单,完成路由地址跳转。

编译时期 arouter-compiler

RouteProcessor是处理路由清单生成的类。其初始化方法如下:

@Override
    public synchronized void init(ProcessingEnvironment processingEnv) 
        super.init(processingEnv);
        // 判断是否需要生成文档,以json的形式生成路由文档
        if (generateDoc) 
            try 
                // ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/docs
                docWriter = mFiler.createResource(
                        StandardLocation.SOURCE_OUTPUT,
                        PACKAGE_OF_GENERATE_DOCS,
                        "arouter-map-of-" + moduleName + ".json"
                ).openWriter();
             catch (IOException e) 
                logger.error("Create doc writer failed, because " + e.getMessage());
            
        

        iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();

        logger.info(">>> RouteProcessor init. <<<");
    

主要是判断是否生成辅助查看的文档。

然后在parseRoutes()中获取@Route注解,解析并生成辅助类。该方法的流程不在深入研究。看一下生成的辅助类结构。

ARouter$$Group$$test.java
ARouter$$Group$$yourservicegroupname.java                               
ARouter$$Root$$app.java
ARouter$$Providers$$app.java

总共生成了以上几个文件,可以分为三类:

  • Root根节点类: 包含多个分组。
  • Group: 包含该组下面的详细的路由地址。
  • Providers : 暂时忽略。

Arouter中有分组的概念。官方文档解释如下:

SDK中针对所有的路径(/test/1 /test/2)进行分组,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化。可以通过@Route 注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组。注意:一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到@Route(path = "/test/1", group = "app")

根据描述,可以看出ARouter$$Root$$app.java就是对分组的统计。该类内容如下:

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

每一个分组的标识又指向了内部路由清单明细的辅助类。

ARouter$$Group$$test.java如下:

public class ARouter$$Group$$test implements IRouteGroup 
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) 
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>()put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); , -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>()put("key1", 8); , -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>()put("name", 8); put("boy", 0); put("age", 3); , -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  

从省城的路由清单里可以看到,最终的路由清单里面,包含了路径已经对应的跳转信息。

跳转信息的类为RouteMeta, 该类结构如下:

public class RouteMeta 
    private RouteType type;         // Type of route 
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination 跳转的目标类
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;

由此看出,上面的路由清单中声明了当前路由是activity,跳转的class等。

以上就是对编译时期的分析。

运行时期 arouter-api

应用运行时需要调用Arouter.init()初始化,该初始化主要用于加载路由清单。


    /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) 
        if (!hasInit) 
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            // 初始化操作
            hasInit = _ARouter.init(application);

            if (hasInit) 
                // 初始化拦截器
                _ARouter.afterInit();
            

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        
    

该逻辑中主要调用了_Arouter.init()_Arouter.afterInit(),afterInit()方法主要用于加载拦截器,不在此次分析的范畴。

 protected static synchronized boolean init(Application application) 
        mContext = application;
        // Logistics 后勤中心
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    

继续深入LogisticsCenter.init(mContext, executor);

 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.
                    // 获取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>()));
                

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                // 把上面加载得到的路由映射根据ClassName分为三种,分别进行注册
                // IRouteRoot 路由的分组辅助类
                // IInterceptorGroup 拦截器
                // IProviderGroup 服务组件
                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);
                    
                
            
    
         

该方法可以分为两个部分。第一个部分是获取编译时期获取的类的全路径列表。第二个部分是分别调用辅助类的loadInto()将路由表信息保存到Warehouse中。

该方法内部流程如下:

  • loadRouterMap()中将registerByPlugin置为false,所以逻辑直接进入到else中。
  • 判断是否是新版本或者是debug,如果是则重新获取辅助类的全路径列表,否则就从缓存里面取。
  • 调用辅助类,将路由信息存储到Warehouse中。

跳转时期 arouter-api

ARouter.getInstance()
                        .build("/test/activity2")
                        .navigation();

跳转的使用方式如上,看一下build方法,该方法最终调用_Arouter.build()方法

   protected Postcard build(String path, String group) 
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) 
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
         else 
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) 
                path = pService.forString(path);
            
            return new Postcard(path, group);
        
    

group如果没有传入,就是默认组名。

PathReplaceServiceArouter提供的一个路径替换的服务,默认是null

最终build()返回了Postcard对象。该对象继承RouteMeta。结构如下。

public final class Postcard extends RouteMeta 
    // Base
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // Data to transform
    private int flags = -1;         // Flags of route
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second
    private IProvider provider;     // It will be set value, if this postcard was provider.
    private boolean greenChannel;
    private SerializationService serializationService;

    // Animation
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim = -1;
    private int exitAnim = -1;

可见该类包含了一些跳转所需要的参数。但是当前只是包含了path等地址标识。然后调用navigation()

该方法最终调用到_Arouter_navigation()方法


    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) 
        try 
            // 根据地址信息匹配出跳转信息
            LogisticsCenter.completion(postcard);
         catch (NoRouteFoundException ex) 
            // 降级处理等 ...
        

        if (null != callback) 
            callback.onFound(postcard);
        
        // 如果不是绿色通多,拦截器做拦截
        if (!postcard.isGreenChannel())    // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            // 拦截器处理等 ...
            
         else 
            return _navigation(context, postcard, requestCode, callback);
        

        return null;
    

该方法首先调用LogisticsCenter.completion(postcard);Warehouse中查询跳转所需要的关键信息。比如目标class等。

然后判断是否是绿色通道,最终都调用了_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:
                // 执行activity跳转
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) 
                    intent.setFlags(flags);
                 else if (!(currentContext instanceof Activity))     // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                

                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) 
                    intent.setAction(action);
                

                // Navigation in main looper.
                runInMainThread(new Runnable() 
                    @Override
                    public void run() 
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    
                );

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try 
                    Object instance &#

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

源码解读Arouter是如何实现的

源码解读Arouter是如何实现的

带你一步一步的解析ARouter 源码

ARouter源码分析

Arouter 源码学习 1

ARouter源码分析—— Provider源码分析