ARouter源码浅析

news/2025/6/19 18:30:08

简介

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

APT

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

annotation

其中Param已被Autowired代替

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

注解解析

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

**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

          // 获得指定包名下的所有类名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 {@AutowiredString name;@Autowiredint age;@Autowired(name = "boy")boolean girl;@AutowiredTestParcelable pac;@AutowiredTestObj obj;private long high;@AutowiredString url;@AutowiredHelloService 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 typeBOOLEAN,BYTE,SHORT,INT,LONG,CHAR,FLOAT,DOUBLE,// Other typeSTRING,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 {@Overridepublic 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.doInterceptionsif (!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*/@Overridepublic void onContinue(Postcard postcard) {_navigation(context, postcard, requestCode, callback);}/*** Interrupt process, pipeline will be destory when this method called.** @param exception Reson of interrupt.*/@Overridepublic 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() {@Overridepublic 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 intentfinal 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() {@Overridepublic void run() {if (requestCode > 0) {  // Need start for resultActivityCompat.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;@Overridepublic 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内核提供了许多的模板类,存储在如下路径中:

那么为什么要提供模板类呢?简单了来说,当我们在变成过程中,由于框架作者并不知道哪些具体类被标注了注解,所以要动态获取对象,只能通过反射动态来获取实例,然后调用接口的方法来执行具体的操作,这就是多态的概念,如下代码所示:
这里插一句,反射是会影响性能的,所以一般我们在编程中除非万不得已,否则尽量不要采用反射,但是这里是activity初始化的时候反射,本来就会进行大量耗时的操作,哪怕有一点点的性能损耗也是可以接受的。还记得Arouter的初始化吗?官网上有一句原话是这么说的:
大家有没有想过,为什么要尽可能早的初始化,我想除了要扫描大量的对象并保存到全局的map集合中以外,跟初始化的时候用到反射也有关系吧,毕竟还是有性能损耗的。如下所示

总结

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

请参考ARouter 源码浅析第二篇


https://dhexx.cn/news/show-3787558.html

相关文章

天堂杂志第337期

【图文故事】最爱瘦西湖 瘦西湖」位于扬州市城西北&#xff0c;六朝以来即为风景胜地&#xff0c;清乾隆时期盛极一时&#xff0c;成为扬州雍容华贵的象征。二千多年历史孕育了这里的文明&#xff0c;多少英雄豪杰、文人墨客在这里留下了动人的故事和不朽的诗篇。 瘦西湖受历…

KeePass使用技巧

KeePass的密码生成器可以自定义“使用样品生成”规则&#xff0c;生成指定格式的随机密码。 自定义一个规则&#xff0c;包含2位大写字母&#xff0c;2位小写字母&#xff0c;4位数字&#xff0c;注意这里控制不了出现的位置&#xff1a; 占位符的含义可以点击帮助文档&#xf…

Spark和Hadoop的架构区别解读

总的来说&#xff0c;Spark采用更先进的架构&#xff0c;使得灵活性、易用性、性能等方面都比Hadoop更有优势&#xff0c;有取代Hadoop的趋势&#xff0c;但其稳定性有待进一步提高。我总结&#xff0c;具体表现在如下几个方面&#xff1a; 框架&#xff1a; Hadoop:MapRedcue由…

centos下LNMP环境搭建

为什么80%的码农都做不了架构师&#xff1f;>>> 新买的云服务器&#xff0c;默认用户只有root&#xff0c;为了防止误操作&#xff0c;还是添加一个管理员为妙。 [rootlocalhost ~]# useradd maohao添加管理员权限 [rootlocalhost home]# vi /etc/sudoers ## Allow…

百度闪电裁员20人被疑违反劳动法

百度闪电裁员20人被疑违反劳动法 <script language"JavaScript" type"text/javascript">function doZoom(size){document.getElementById(pzoom).style.fontSizesizepx;}</script> ◇字体&#xff1a;&#xff3b;大 中 小&#xff3d; 发表评…

深度学习时代的计算机视觉

人工智能&#xff0c;作为计算机科学的一个分支。 从1956年夏季麦卡赛、明斯基、罗切斯特和申农等一批有远见卓识的年轻科学家首次提出&#xff0c;到2006年机器学习泰斗Geoffrey Hinton和他的学生RuslanSalakhutdinov在《科学》上发表了一篇开启深度学习在学术界和工业界浪潮…

《软件测试管理》第14章 软件测试常见问题——(一)基础知识部分

基础知识常见问题1、 如何描述一个缺陷&#xff1f;看到这个问题&#xff0c;也许有些读者会觉得可笑&#xff1a;哪个测试人员不会描述缺陷&#xff1f;但是现实中却真的存在很多测试人员提交的缺陷需要向开发人员进行解释或者演示后&#xff0c;才能让人明白他真正要表达的意…

只有初学者才能看懂的 Spring Boot

技术语言革新极快的今天&#xff0c;尤其对于需要技术沉淀的后端工程师来说&#xff0c;靠什么实力逆风翻盘&#xff1f; 在 Java 框架尚且繁荣的当下&#xff0c;Spring Boot 无疑最火最实用的&#xff0c;也是必不可少的开源框架&#xff0c;完全有实力稳坐 Java 后端框架的…

ATM跨行查询也开始收费了

昨天看自己的农户帐单记录&#xff0c;发现有两笔0.3元&#xff0c;原来跨行查询也开始收费了。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId913385