ARouter探究(一)

ARouter探究(一)

前言

  • ARouter 是 Alibaba 开源的一款 Android 页面路由框架,特别适用于模块化开发过程中,页面跳转所带来的耦合问题。由于自己才疏学浅,有理解不当之处还请指教。
  • 简介
    别人有详细的介绍,这就不写了

技术准备

几个问题

  • 怎么支持多模块的?

    1. 首先在编译期怎么知道多个模块怎么加载,我们看下ARouter怎么做到的?
      • 既然是编译期我们猜想肯定用到APT,在Apt技术中可以通过在buidGradle文件中拿到参数,然后交给Process进行处理。看下ARouter是不是这样做的?
        1. build.gradle文件中配置
           javaCompileOptions {
          annotationProcessorOptions {
              arguments = [ moduleName : project.getName() ]
          }
          }
          
        2. 我们可以在AbstractProcessor类中的init(ProcessingEnvironment processingEnv)方法里面获取配置
           Map<String, String> options = processingEnv.getOptions();
          if (MapUtils.isNotEmpty(options)) {
              moduleName = options.get(KEY_MODULE_NAME);
          }
          
        果然是这样。
  • 怎么加载模块的数据的呢?

    • 针对路由存在路由表,那么必然存在初始化的过程?

      • ARouter.init(getApplication()); ARouter的初始化此过程是初始化路由表信息。我们看下做了啥?
        我们发现他调用了
          __ARouter.init(application)
        
        此处ARouter用了代理模式,实际使用了_Arouter的init方法。我们再进去看发现
         LogisticsCenter.init(mContext, executor);
        
        初始化交给了LogisticsCenter这个类,它是处理路由跳转的核心类。init方法里如下
        final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
        List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
        for (String className : classFileNames) {
            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);
            }
        }
        
        这是什么鬼?看到这里我们可能要蒙了,这是干什么的。因为我们前面说了init肯定要把路由信息加载到我们要存储的地方。 ClassUtils.getFileNameByPackageName()方法,我们根据方法名可以判断是遍历包名下的文件拿到我们Clas名,这个包名就是我们apt生成文件所在的包下,我们先看几个类APT生产的类目录结构如下?
      image
      • 打开ARouter$$Root$$app
        public class ARouter$$Root$$app implements IRouteRoot {
          @Override
          public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
            routes.put("service", ARouter$$Group$$service.class);
            routes.put("test", ARouter$$Group$$test.class);
          }
        }
        
        对于刚才LogisticsCenter.init方法里的
         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);
                }
        
        这个地方就是利用反射生成ARouter$$Root$$app对象,调用loadinto方法最后把内容传给了Warehouse.groupsIndex。WareHouse是干啥的呢?看代码
        class Warehouse {
            // Cache route and metas
            static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
            static Map<String, RouteMeta> routes = new HashMap<>();
        
            // Cache provider
            static Map<Class, IProvider> providers = new HashMap<>();
            static Map<String, RouteMeta> providersIndex = new HashMap<>();
        
            // Cache interceptor
            static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
            static List<IInterceptor> interceptors = new ArrayList<>();
        
            static void clear() {
                routes.clear();
                groupsIndex.clear();
                providers.clear();
                providersIndex.clear();
                interceptors.clear();
                interceptorsIndex.clear();
            }
        }
        

    我们看到这里就是我们路由表缓存信息,交给对应的集合存储,这里groupsIndex就存了我们的分组信息。我们看到这里初始化的时候只是把分组存进去了,但具体的路由信息并没有加载进来,==这符合作者说的按组分类,按需加载提高查找效率,但是什么时候加载呢,我们先留个问号?后边会讲述==

  • 路由怎么完成查找的的?首先看用例

     ARouter.getInstance().build("/test/activity2").navigation();
    
    • .build("/test/activity2")源码如下
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }
    

    ==1==我们看到他还是交给了_ARouter进行处理,他的build方法如下:

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

    ARouter.getInstance().navigation(PathReplaceService.class);我们先看PathReplaceService是干啥的

    public interface PathReplaceService extends IProvider {
    
    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path);
    
    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    Uri forUri(Uri uri);
    }
    

    ==2==其实就是预处理我们的path这是重定向用的。上面的navigation()方法肯定也是交给_ARouter进行处理的我们看下他的方法

    protected <T> T navigation(Class<? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());
    
            // Compatible 1.0.5 compiler sdk.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }
    
            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }
    

    看到这里我们看到最后还是交给了我们的核心逻辑处理类LogisticsCenter处理Postcard postcard = LogisticsCenter.buildProvider(service.getName()); 此处是创建个Postcard,它是个一个container里面有我们整个跳转所用到的所有信息包括跳转动画,看看buildProvider做了什么

    public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);
    
        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
    }
    

    首先他先从warehose里面取出我们重定向的path和group重新生成Postcard,如果没有的话直接返回null,这里我们没有,所以就返回了null然后是 LogisticsCenter.completion(postcard);看代码

    if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }
    

    如果为null直接抛出了异常,在哪里处理的呢,在黄字==2==的下面,然后catch异常返回了null,再回到黄字==1==处,我们走到了

    build(path, extractGroup(path)) 
    

    然后创建了PostCard.这就build完了

    • ARouter.getInstance().build("/test/activity2").navigation();==3==然后就是navigation方法,他也是有_ARouter进行处理
     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());
    
            if (debuggable()) { // Show friendly tips for user.
                Toast.makeText(mContext, "There's no route matched!\n" +
                        " Path = [" + postcard.getPath() + "]\n" +
                        " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
            }
    
            if (null != callback) {
                callback.onLost(postcard);
            } else {    // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }
    
            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() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }
    
                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }
    
                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }
    
        return null;
    }
    

    还是这个类

     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()));
                    }
    
                    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());
            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 autoinject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }
    
                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            } 
    }
    

    这就是我们的重点所在,通过上面可知我们的postcard走到这里不会为null了但是RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());这个返回的是null的因为我们是第一次加载这个分组里的routeMeta,所以进入if语句 ==Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());查找我们PostCard里面带的分组,然后是IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
    iGroupInstance.loadInto(Warehouse.routes);
    这里就加载了我们的组内元素,也就是实现了上面所说的按需加载==,然后就completion(postcard);这时候重走方法,RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());routeMeta就有值了我们就找到了我们要的路由信息,也就是通过path找到了routeMeta.就有了我们要给PostCard附上我们的目的地信息,参数信息等。
    然后这就走完了

    • 有人要问跳转呢?我们回到黄字3处,return _navigation(context, postcard, requestCode, callback)最后走到这里,我们看下源码
     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);
                }
    
                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }
    
                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }
    
                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });
                }
    

    在主线程中startActivity,终于找到我们的调整目的地。写一篇会介绍依赖注入和拦截器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容