_ARouter 的 navigation(Class<? extent T> service) 方法分析

利用 ARouter 实现页面跳转时是会涉及到 ARouter 的 build 方法以及 Postcard 的 navigation 方法。这次先从 build 方法分析一下实现过程。

build 方法内最终会调用 _ARouter 的 navigation(Class<? extent T> service) 方法,我看比较复杂,所以就细看一下,

protected <T> T navigation(Class<? extends T> service) {
    try{
        Postcard postcard = LogisticsCenter.buildProvider(service.getName());
        if(null == postcard) {
            //这个判断是为了兼容老版本
            postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        }
        if(null == postcard) {
            return null;
        }
        LogisticsCenter.completion(postcard);
        return (T)postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        return null;
    }
}

方法的大概意思是借助入参的 .class 文件,编译生成对应类的对象返回。实现过程是利用 Postcard 类,先创建出 Postcard 类型对象,再通过对 Postcard 对象的 completion,最后从 Postcard 对象里获取对应类的对象实例。

分析过程根据代码大致分三步。

第一步

Postcard postcard = LogisticsCenter.buildProvider(service.getName());
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());
    }
}

这个方法执行需要先了解一个类 Warehouse,字面翻译是仓库,类解释也差不多是这个意思,

//Storage of route meta and other data,用来存储 route 元素和其他数据
class Warehouse {
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();
    
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();
    
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();
}

看样子主要就是三大部分数据,groupsIndex 和 routes 表示和路径路由相关的数据,providers 和 providersIndex 表示提供器对象一类的数据(这个目前不太懂,先记下),interceptorsIndex 和 interceptors 表示拦截器对象数据。

所以 buildProvider 的工作就是从仓库里找到对应类的 RouteMeta 对象,如果存在就用 RouteMeta 对象信息创建一个 Postcard 对象并返回,没有的话就返回 null,null 的话就表示要么异常,要么不需要。

既然是仓库,那仓库里的 providersIndex 的值是怎么来的?

通过引用查找,发现前面的 LogisticsCenter 初始化用到,ARouter 会事先扫描加载 routes 包下面的类,将类名缓存在一个 Set 集合里。

然后再遍历这个集合,来操作存储指定值到 providersIndex 里,一起执行的还有 groupsIndex 和 interceptorsIndex。

这里有个亮点就是,考虑到从文件里扫描加载 routes 包的内容是个低效率,耗资源的过程,ARouter 采用了 SP 缓存机制,这里有两个缓存,一个是控制要不要重新扫描加载的版本,一个就是包下面的类名集合。

所以在缓存之后,只要 ARouter 版本不改变就不会再次去扫描加载了,因为本身这块也不会频繁变动,也就提高了效率。(但开发阶段除外,其实这个也可以控制是不是开发阶段)


routes包下面的类.png

遍历的时候就是去匹配类名里的关键字段,然后应该是用到了反射原理,getConstructor().newInstance() 来创建

ARouter$$Providers$$arouterapi

这个类的对象,并执行 loadInfo() 方法,完成了 providersIndex 赋值。

public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
}

第二步

LogisticsCenter.completion(postcard);
public synchronized static void completion(Postcard 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());
        if (null == groupMeta) {
            throw new NoRouteFoundException("xxx");
        } else {
            try{
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);
                Warehouse.groupsIndex.remove(postcard.getGroup());
            }
            completion(postcard);
        }
    } else {
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        
        Uri rawUri = postcard.getUri();
        if (null != rawUri) {
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();
            if (MapUtils.isNotEmpty(paramsType)) {
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                }
                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }
        switch (routeMeta.getType()) {
            case PROVIDER:
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) {
                    IProvider provider;
                    try{
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();
                break;
            case FRAGMENT:
                postcard.greenChannel();
                break;
            default:
                break;
        }
    }
}

这个方法代码比较多,分几个步骤来了解,

  1. 根据入参 postcard 的 path 路径从仓库的 routes 里获取路由信息
  2. 判断该路由信息是否存在,如不存在就需要加载,如存在就从路由信息里取出一些数据赋值给 postcard 对象,其中有两个值需要重点关心,uri 和 type。
  3. 如不存在,会从仓库里的 groupsIndex 里加载组信息,通过组信息创建路由组对象来加载该组的路由信息,然后递归执行 completion 方法。
  4. 如存在,会重点对 uri 进行参数赋值,具体会利用 Bundle 进行存储参数,接着根据 type 进行操作,具体处理了两种类型 PROVIDER 和 FRAGMENT。如果是 PROVIDER 类型,会创建对应的实例存入仓库中的 providers 里,并且会将该实例赋值给 postcard。

以上就是整个方法的大致逻辑,从上面得出方法的结束肯定是能根据 path 找到对应路由信息,要不然就抛异常了。我们暂且先不具体到代码分析执行,因为这可能涉及到 Java 注解的代码生成,目前还不会,晚点再看。

第三步

return (T)postcard.getProvider();

navigation() 方法的最后一步就是返回 postcard 对象的 provider 对象。

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

推荐阅读更多精彩内容