ARouter 源码浅析

简介

Android平台中对页面、服务提供路由功能的中间件,我的目标是 —— 简单且够用。具体的使用可以参考:https://github.com/alibaba/ARouter。如果对内部的详细原理感兴趣可以参考:http://www.jianshu.com/p/3c4f4e3e621f。下文分析的代码来源于ARouter官方Demo,阅读下文之前建议先下载一份源码运行并对照源码进行比对,因为下文中我会用官方demo的源码截图。

APT

APT技术可以让我们通过自定义注解动态生成编译后的class代码,具体的使用我在这里就不详细说了,感兴趣的可以参考我以前写的:编写最基本的APT Demo
我这里直接来看下ARouter说涉及到的几个注解,以及编译之后动态生成的代码。

annotation

image.png

其中Param已被Autowired代替

Route:用于标记我们需要跳转的四大组件(可以通过Intent跳转的,因为其实ARouter 内部最后也是通过Intent来进行跳转)、service(此处的sevice是类似于后台的服务,需要继承IProvider)。
Interceptor:主要的作用是通过AOP技术(面向切面编程)在我们进行页面跳转之前可以进行一系列的拦截操作
Autowired:主要的作用是通过IOC技术(依赖注入)获取页面跳转的参数。

注解解析

image.png

可以看到对应了上面的三个注解。这里具体的代码我就不分析了,感兴趣的可以直接去看源码(虽然不算很难但是比较繁琐,一定要耐心),当我们全局编译以后会动态生成以下代码


image.png

ARouter$$Root$$app:因为Arouter采取的是懒加载技术,所以我们需要对router进行分组,这里的Root内部就是通过Map以组名为key存储了每组router的信息信息。
ARouter$$Group$$xxx:我们按不同的router类型进行分组,内部通过Map以router path存储了具体router的信息
ARouter$$Interceptors$$app:其中app是我们的module名(通过查看源码可知),内部
以priority优先级为key存储了具体的Interceptors的class信息。
ARouter$$Providers$$app:其中app是我们的module名(通过查看源码可知),内部以类的完整限定名为key保存了service的信息,结构同ARouter$$Group$$xxx一致,只是用于不同的功能。

关于ARouter的APT分支就到这了,下面来看下ARouter的初始化。

init

这正式分析初始化之前我们先了解几个类

ARouter:_ARouter的代理类,这里采用了代理模式,其实ARouter对外只开放了这一个api,所有的操作基本上都是通过ARouter来完成了。
_ARouter:ARouter所代理得到类,功能的具体执行者。
LogisticsCenter:物流调度中心,对路由信息进行解析和加工。
Warehouse:仓库,存储了所有具体的router、interceptors、service等信息,内部是一系列的Map。

ARouter的初始化流程:ARouter#init ──》_ARouter#init ──》LogisticsCenter#init ──》Warehouse#Map#put


image.png
          // 获得指定包名下的所有类名
            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. 通过反射找到Arouter$$Root$$xx 加载根路由集合
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta 通过反射找到Arouter$$Interceptors$$xx 加载拦截器集合
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex 通过反射找到Arouter$$Providers$$xx 加载服务集合 此处的service对应后台的概念 不是四大组件的service
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

其中第三步和第四步的代码会根据指定报名查找下面所有的类信息,然后根据类得到全限定名进行功能分组,并把信息保存在Warehouse的Map中。到此Arouter的初始化过程就完成。

Router的跳转和参数注入

ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .navigation();

public class Test1Activity extends AppCompatActivity {

    @Autowired
    String name;

    @Autowired
    int age;

    @Autowired(name = "boy")
    boolean girl;

    @Autowired
    TestParcelable pac;

    @Autowired
    TestObj obj;

    private long high;

    @Autowired
    String url;

    @Autowired
    HelloService helloService;

}

其中Test1Activity 通过APT动态生成的代码如下:

atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0); put("age", 3); put("url", 8); }}, -1, -2147483648));

所有的跳转参数都保存在map中,其中key是一一对应,而value是参数类型,对题对照如下:

public enum TypeKind {
    // Base type
    BOOLEAN,
    BYTE,
    SHORT,
    INT,
    LONG,
    CHAR,
    FLOAT,
    DOUBLE,

    // Other type
    STRING,
    PARCELABLE,
    OBJECT;
}

下面我们来看具体的跳转流程。

build

ARouter.getInstance().build("/test/activity1")升温我们说过ARouter是_ARouter的代理的类,所有的api最终都会进入到真正的执行类_ARouter。
_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));
        }
    }

这里我们先来看下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);
}

PathReplaceService我称它为重定向服务(具体怎么使用请参考官网文档),它继承IProvider,那IProvider是什么,其实他是用来实现service的,所以PathReplaceService就相当于我们自己的自定义服务,唯一的区别是,自定义服务需要我们显示去调用,跟调用router一样,但是PathReplaceService不需要显示调用,他是作用于所有服务和路由的,而且不管你实现了几个PathReplaceService,最终全局都只会保存在APT时扫描到的最后一个服务。为什么这么说,请看下面的代码:

public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    //第一个重定向服务
    providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/redirect/r1", "redirect", null, -1, -2147483648));
    //第二个重定向服务
    providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl2.class, "/redirect/r2", "redirect", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
  }
}

因为第一个和第二个重定向服务的key是一样都是PathReplaceService的类全限定名,所以第一个服务会被覆盖掉。好了关于PathReplaceService我们就说到这,他是我们一个重定向服务,作用域所有的跳转,而且全局只有一个。
我们继续往下分析,如果单例没有实现自定义重定向服务的时候,PathReplaceService pService == null,所以会直接调用两个参数的重载的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);
        }
    }

这里有调用了一遍ARouter.getInstance().navigation(PathReplaceService.class),感觉没必要啊,但是不管肯定还是一样的返回空。所以最终ARouter.getInstance().build()会返回一个Postcard(个包含跳转信息的容器,包含跳转参数和目标类的信息)。下面进入真正的跳转。其实真正的跳转位于_ARouter#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) {
           ……………………
            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }
        //不是绿色通道所以会进入默认interceptorService.doInterceptions
        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;
    }

InterceptorService是我们在初始化完成以后ARouter为我们自动注册的拦截器服务,因为我们并没有为我们得到路由匹配相应的拦截器,所以应该会进入onContinue方法,经过断点调试确实和我们想的一样,可是onContinue是个回调函数,它又具体是在哪被调用的呢?我们经过查找发现是在InterceptorServiceImpl中

InterceptorServiceImpl#doInterceptions

LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });

这里开了一个线程用来执行拦截器或者普通的跳转所以调用了callback.onContinue,接下来就进入到我们真正的跳转执行的地方了。

_ARouter#_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);
                }

                // 因为上文我们是在子线程中检查是否有匹配的拦截器,所以我们要在这里切换到UI线程执行具体的跳转
                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);
                        }
                    }
                });

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

这里的代码比较简单,就是调用了Android原生的Intent进行跳转,然后根据不同的状态,调用一些回调函数。到此关于ARouter的跳转到这里就结束了,下面我们来看下目标对象的参数是如何获取的。

Autowired

这里在分析参数获取之前我们先废话2句,在看到Autowired注解的时候,是不是感觉似曾相识,没错这里的原理跟ButterKnife是一毛一样的,我强烈怀疑Arouter作者是参考ButterKnife代码写的,所以当我们分析完Autowired的时候,其实就相当于把ButterKnife也给分析了,哈哈,正式一举两得啊。还有,这种开发思想其实在后台开发中非常普遍,比如大名鼎鼎的Spring就是这种IOC(控制反转)思想的最佳代表。好了,下面进入正题。

Autowired注解处理器

当我们在编译过程中,系统会是扫描有Autowired注解的成员变量类,然后生成自动生成以下代码:

public class Test1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);;
    Test1Activity substitute = (Test1Activity)target;
    substitute.name = substitute.getIntent().getStringExtra("name");
    substitute.age = substitute.getIntent().getIntExtra("age", 0);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", false);
    substitute.pac = substitute.getIntent().getParcelableExtra("pac");
    if (null != serializationService) {
      substitute.obj = serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    substitute.url = substitute.getIntent().getStringExtra("url");
    substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
  }
}

这里的代码很简单,应该能直接看懂,我们先来看他的父类ISyringe,他其实相当于一个模板类,为了便于编程ARouter内核提供了许多的模板类,存储在如下路径中:


image.png

那么为什么要提供模板类呢?简单了来说,当我们在变成过程中,由于框架作者并不知道哪些具体类被标注了注解,所以要动态获取对象,只能通过反射动态来获取实例,然后调用接口的方法来执行具体的操作,这就是多态的概念,如下代码所示:


image.png

这里插一句,反射是会影响性能的,所以一般我们在编程中除非万不得已,否则尽量不要采用反射,但是这里是activity初始化的时候反射,本来就会进行大量耗时的操作,哪怕有一点点的性能损耗也是可以接受的。还记得Arouter的初始化吗?官网上有一句原话是这么说的:
image.png

大家有没有想过,为什么要尽可能早的初始化,我想除了要扫描大量的对象并保存到全局的map集合中以外,跟初始化的时候用到反射也有关系吧,毕竟还是有性能损耗的。如下所示


image.png

image.png

总结

好了,到这我们已经把页面跳转和参数绑定都分析完了,剩下的重定向,拦截器,降级等很多其他功能,其实都是在跳转的过程中插入的一些拦截操作而已,我相信只要大家只要耐下心来看代码都是可以看明白的。
请参考ARouter 源码浅析第二篇

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