美文网首页
Arouter框架原理浅解

Arouter框架原理浅解

作者: 泡花茶 | 来源:发表于2020-12-29 17:19 被阅读0次

    一、什么是路由?

    路由是指路由器从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。—百度百科

    以Android为例,各个组件module可以看成一个个网络,而路由就可以看成是各个模块页面跳转的中转站,除了中转以外,路由还可以过滤或拦截一些不安全或不规范的跳转。

    二、为什么用阿里ARouter

    官方定义:A framework for assisting in the renovation of Android componentization (帮助 Android App 进行组件化改造的路由框架)

    其实Android的跳转已经提供了Intent,Intent不仅可以显示跳转,也可以隐式跳转。那么ARouter为什么还会存在呢?让我们对比一下原生跳转和路由跳转:

    1、原生显示跳转必须依赖于类,强耦合,而隐式跳转需要都在AndroidManifest里集中注册,协作麻烦;而路由跳转通过URL索引,低耦合,协作方便;

    2、原生在通过startActivity启动跳转后就交由系统控制,并不能做拦截;路由跳转可以对跳转做过滤或拦截。

    6eb2eafbd4c419c493dbbfb1b05064087cda2173.png

    通过对比,我们发现路由特别适合用来做跳转(尤其是组件间的通信及跳转),下面就来看一下阿里ARouter的集成及使用吧~

    三、ARouter概述

    ARouter 是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

    适用于以下场景:

    从外部URL映射到内部页面,以及参数传递与解析

    跨模块页面跳转,模块间解耦

    拦截跳转过程,处理登陆、埋点等逻辑

    跨模块API调用,通过控制反转来做组件解耦

    主要包括这些功能:

    支持直接解析标准URL进行跳转,并自动注入参数到目标页面中

    支持多模块工程使用

    支持添加多个拦截器,自定义拦截顺序

    支持依赖注入,可单独作为依赖注入框架使用

    支持InstantRun

    支持MultiDex(Google方案)

    映射关系按组分类、多级管理,按需初始化

    支持用户指定全局降级与局部降级策略

    页面、拦截器、服务等组件均自动注册到框架

    支持多种方式配置转场动画

    支持获取Fragment

    完全支持Kotlin以及混编(配置见文末 其他#5)

    支持第三方 App 加固(使用 arouter-register 实现自动注册)

    支持生成路由文档

    提供 IDE 插件便捷的关联路径和目标类

    ARouter使用

    这里只介绍ARouter最基础的用法–Activity跳转,其他用法如通过URL跳转、解析参数、声明拦截器、处理跳转结果、声明服务等,请详细阅读ARouter官网使用说明

    1、添加依赖和配置

    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [AROUTER_MODULE_NAME: project.getName()]
                }
            }
        }
    }
    dependencies {
        // 替换成最新版本, 需要注意的是api
        // 要与compiler匹配使用,均使用最新版可以保证兼容
        compile 'com.alibaba:arouter-api:x.x.x'
        annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    }
    // 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'
    // Kotlin配置参考文末'其他#5'
    

    2、添加注解

    // 在支持路由的页面上添加注解(必选)
    // 这里的路径需要注意的是至少需要有两级,/xx/xx
    @Route(path = "/test/activity")
    public class YourActivity extend Activity {
        ...
    }
    

    3、初始化SDK

    if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
        ARouter.openLog();     // 打印日志
        ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
    }
    ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
    

    4、发起路由操作

    // 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
    ARouter.getInstance().build("/test/activity").navigation();
    // 2. 跳转并携带参数
    ARouter.getInstance().build("/test/1")
                .withLong("key1", 666L)
                .withString("key3", "888")
                .withObject("key4", new Test("Jack", "Rose"))
                .navigation();
    

    四、Arouter核心思路

    ARouter通过Apt技术(APT,就是Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。),生成保存路径(路由path)和被注解(@Router)的组件类的映射关系的类,利用这些保存了映射关系的类,Arouter根据用户的请求postcard(明信片)寻找到要跳转的目标地址(class),使用Intent跳转。
    原理很简单,可以看出来,该框架的核心是利用apt生成的映射关系,这里要用到Apt技术,读者可以自行搜索了解一下。

    分析

    在自定义Application中进行初始化:
    // 尽可能早,推荐在Application中初始化

    ARouter.init(Application.this);
    

    最终调用了LogisticsCenter.init(mContext, executor)

    //LogisticsCenter.java
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    
          Set<String> routerMap;
          
          //1、遍历“com.alibaba.android.arouter.routes”路径下的类并把其加入到set中
          if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
              // 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();
              }
             // Save new version name when router map update finishes.
             PackageUtils.updateVersion(context);    
          } 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>()));
                }
                
                
          //2、遍历set,将root、group、provider分类并填充到Warehouse路由表中
          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);
              }
          }
      }
    }
    

    LogisticsCenter.init方法比较长,上面只保留了核心代码,我们来看上面这种加载方式,PackageUtils.isNewVersion(context)中判断SharedPreferences(后面简称sp)里面是否有存储versionName及versionCode,如果没有或者他们有更新的时候,需要重新加载一次com.alibaba.android.arouter.routes这个路径下的类名并填充到Set中,否则直接从sp中取数据并赋值到Set中去。接着就开始遍历这个Set,并通过Class.forName(className)这种反射方式去实例化类并调用类中的loadInto方法将注解对应的索引信息添加到Warehouse路由表中。画个图来总结一下:


    587163-75a58236057aacee.webp.jpg

    ARouter跳转

    ARouter跳转时,直接使用ARouter.getInstance().build("xxx/xxx").navigation()即可完成跳转,那我们就来看一下源码,看看里面都做了什么,首先是build方法:

    /**
     * Build postcard by path and default group
     */
    protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
    }
    
    /**
     * Build postcard by uri
     */
    protected Postcard build(Uri uri) {
        if (null == uri || TextUtils.isEmpty(uri.toString())) {
            throw new HandlerException(Consts.TAG + "Parameter invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                uri = pService.forUri(uri);
            }
            return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
        }
    }
    
    /**
     * Build postcard by path and group
     */
    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);
        }
    }
    

    三个方法中都有,PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class),这个类是用来预处理path和uri的,调用方需要实现PathReplaceService就可以做预处理,如果不实现,默认pService==null,那么直接走下面的去初始化Postcard实体类。

    接着来看navigation方法,因为build方法返回的是PostCard类,所以调用的是PostCard类的navigation方法,经过一系列跳转,最终来到_ARouter.getInstance().navigation(mContext, postcard, requestCode, callback) :

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
          } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
         }
            return null;
        }
    
        if (null != callback) {
            callback.onFound(postcard);
        }
    
        if (!postcard.isGreenChannel()) {   
           // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
         
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }
    
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }
        return null;
    }
    

    去除了部分无关代码,只保留了核心代码,首先调用了LogisticsCenter.completion方法,我们追进去看看:

    //LogisticsCenter.java
    /**
     * Completion the postcard by route metas
     *
     * @param postcard Incomplete postcard, should complete by this method.
     */
    public synchronized static void completion(Postcard postcard) {
        // 从仓库的路由地址清单列表中拿到对应的RouteMeta
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    
            // 根据一级地址,拿到对应的路由地址清单的文件类(ARouter$$Root$$工程名)
            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 {
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());
    
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
    
                completion(postcard);   // Reload
            }
        } 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) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }
    

    这个类很长,但是逻辑还是很清晰的:首先从Warehouse路由表的routes中获取RouteMeta,但是第一次获取的时候为空(因为init时只填充了Warehouse路由表的groupsIndex、interceptorsIndex、providersIndex,还记得吗?),接着从Warehouse.groupsIndex中根据group的名字找到对应的group索引,并将生成的索引类的map数据加载到Warehouse.routes中,然后把Warehouse.groupsIndex中对应的group删除掉,以免重复加载数据,然后调用了completion(postcard)进行重新加载。此时Warehouse.routes已经不为空,根据path获取对应的RouteMeta,就会走到else逻辑中,先是对PostCard设置了一堆属性,最后对IProvider的子类进行了初始化并加载到Warehouse.providers中,同时也设置到PostCard中,并给PROVIDER和FRAGMENT设置了绿色通道(不会被拦截)。总结一下:主要逻辑就是通过Warehouse.groupsIndex找到对应的group并进行加载,实现了分组加载路由表。

    我们继续回到navigation方法中往下走,首先通过postcard.isGreenChannel()判断是否会拦截,如果拦截,就会走interceptorService的逻辑(interceptorService是在afeterInit中初始化的),否则就走到了_navigation逻辑中,那么来看_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
                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 = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
    
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }
    
        return null;
    }
    
    private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  
            // Need start for result
            if (currentContext instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            }
        } else {
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }
    
        if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }
    
        if (null != callback) { // Navigation over.
            callback.onArrival(postcard);
        }
    }
    

    最终ARouter跳转Activity最终也是用原生的Intent实现的,如果navigation()不传入context,则使用初始化时Application作为context,如果是FRAGMENT、PROVIDER、CONTENT_PROVIDER、BOARDCAST,通过反射方式初始化并返回即可。

    源码分析之拦截处理

    参考文档

    链接:路由框架ARouter使用及源码解析
    链接:ARouter源码解析
    链接:ARouter 官方文档

    相关文章

      网友评论

          本文标题:Arouter框架原理浅解

          本文链接:https://www.haomeiwen.com/subject/imcqoktx.html