博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
阿里巴巴开源路由框架 - ARouter 分析
阅读量:7256 次
发布时间:2019-06-29

本文共 11448 字,大约阅读时间需要 38 分钟。

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

技术准备

源码分析

我们直接看官方 demo,核心模块包括api,compiler,annotation, 首先先简单讲一下各模块的作用:

  • Annoation: 定义注解和 Route 相关的基本信息
  • Compiler: 主要是用来在编译期间处理注解Router/Interceptor/Autowire三个注解,在编译期间自动注册注解标注的类,成员变量等。
  • Api: 用户调用的核心 api

annoation

Autowired 用于注解 field, 可实现路由件传递参数的自动注入,无需使用者在代码中去获取传递的参数,注意,注解对象的修饰符不能是 private。

Interceptor 用于注解拦截器,拦截器的概念请参考官方文档

Route 路由注解,可以是 Activity,也可以是 Service

RouteMeta 路由相关的数据,包括路由类型,路径,组,权重,参数等信息

compiler

RouteProcessor

路由注解器

关键方法 #parseRoutes#,我们先看这个方法最后生成的文件到底是啥,弄清楚了才能更容易的理清代码。

public class ARouter$$Root$$app implements IRouteRoot {    public ARouter$$Root$$app() {    }    public void loadInto(Map
> routes) { routes.put("service", service.class); routes.put("test", test.class); }}复制代码

注意map 存入的key 分别是 service 和 test,valude 则对应为 ARouter$$Group$$service.class 和 ARouter$$Group$$test.class。一个组件有且只有一个 Root 类,不同的组件以后缀作为区分。

public class ARouter$$Group$$test implements IRouteGroup {    public ARouter$$Group$$test() {    }    public void loadInto(Map
atlas) { atlas.put("/test/*", RouteMeta) }}复制代码
public class ARouter$$Group$$service implements IRouteGroup {    public ARouter$$Group$$service() {    }    public void loadInto(Map
atlas) { atlas.put("/service/*", RouteMeta) }}复制代码

注意 Arouter$$Group$$test 中 map 的 key 必然是以 "/test" 为前缀,刚好与其后缀相同

public class ARouter$$Providers$$app implements IProviderGroup {    public ARouter$$Providers$$app() {    }    public void loadInto(Map
providers) { providers.put(className, RouteMeta) }}复制代码
public class ARouter$$Interceptors$$app implements IInterceptorGroup {    public ARouter$$Interceptors$$app() {    }    public void loadInto(Map
> interceptors) { interceptors.put(privority, Class); }}复制代码

小结一下:

  1. 在一个module中,自动生成的 IRouteRoot 类有且只有一个,后缀为 module 名
  2. 在一个module中,自动生成的 IRouteGroup 类可以有多个,且都会在 IRouteRoot 类中注册
  3. IProviderGroup 只有一个,后缀为 module 名

准确来说后缀并不是 module 名,实际上你可以在 gradle 中指定,后续在讲解 parseRoutes 方法时会讲到具体原因

接下来将讲解 RouteProcessor#parseRoutes 方法:

  1. 拿到 Route,根据 RouteType 构建 RouteMeta,并通过 #categoryies 方法构建映射关系存储到 groupMap(groupMap 的 key 为 routeName ,value 则是 RouteMeta 集合)
  2. 遍历 groupMap 创建 ARouter$$Group$$groupName 类, 同时将 groupName 与生成的 File 的全类名的映射关系存储到 rootName
  3. 创建 ARouter$$Providers$$moduleName 类
  4. 创建 ARouter$$Rout$$moudleName

其中 groupName 就是你在注解中设置的 group,但我们平时在使用 ARouter 的时候很少回去设置 group, 如果你没有指定 group,Arouter 会默认使用 path 的主路径,比如你指定了 path = "/test/main", 那 groupName 就是 test; moudleName 则由你在 gradle 中指定。

AutoWiredProccesor

首先看一下生成的文件:

public class Test1Activity$$ARouter$$Autowired implements ISyringe {    private SerializationService serializationService;    public Test1Activity$$ARouter$$Autowired() {    }    public void inject(Object target) {        this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);        Test1Activity substitute = (Test1Activity)target;        substitute.name = substitute.getIntent().getStringExtra("name");       ...        substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);    }}复制代码

生成的 class 继承之 ISyring只有一个方法 inject(Object),该方法的实现主要做了两件事:

  1. AutoWired 注解的 filed 如果是 service (Arouter 中继承自 IProvider), 则初始化对象:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);复制代码
  1. AutoWried 注解的 filed 非 service,则从 bundle 中获取相应的 value 赋值给该 field:
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class); // activity	substitute.name = substitute.getArguments().getString("name"); // fragment复制代码
  1. 最后生成类名$$Arouter$$AutoWired.class 文件

InterceptorProccessor

拦截注解生成器

public class ARouter$$Interceptors$$app implements IInterceptorGroup {    public ARouter$$Interceptors$$app() {    }    public void loadInto(Map
> interceptors) { interceptors.put(7, Test1Interceptor.class); }}复制代码

api

ARouter & _ARouter

launcher 下只有两个类 ARouter, _ARouter, ARouter 采用单例模式,对外提供 ARouter 相关 API,我们在调用ARouter.getInstance().build()时 真正调用的是_ARouter的相关方法,这里采用了代理模式,现在我们就开始以 ARouter 向外提供的方法来逐步分析:

首先看init(): 在 Arouter 库中,有一个 Warehose类,它存储了路由相关的信息,我们在使用 ARouter 之前必须要先调用 init 方法,而 init方法就是需要将信息存储到 Warehose 中 以下为时序图:

通过调用#loadInto(Warehose)方法,成功地将信息存储到 Warehose

现在Warehose 的 groupsIndex, providersIndex, interceptorsIndex 已经被我们存入了路由信息

最后再调用_ARouter#afterInit()实例化 interceptorService

接下来是 navigation()方法:

我们需要先了解Postcard 类,Postcard 继承 RouteMeta,扩展了一些属性,例如:flag(activity 的 flag),uri,provider,anim(动画)。我们平时在是用 Arouter 时 build 方法返回的就是 Postcasrd, postcard#navigation 方法,最终调用的是就是 _Arouter#navaigation

LogisticsCenter.completion(postcard);if(!postcard.isGreenChannel()) {    // 拦截器相关,暂时跳过    interceptorSevice.doInterceptions(post, new InterceptorCallback() {        @Override        public void onContinue(Postcard postcard) {            _navigation(context, postcard, requestCode, callback);        }    })} else {    return _navigation(context, postcard, requestCode, callback);}复制代码

首先我们来看LogisticsCenter#completion(Postcard)方法

...RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());if (null == routeMeta) {    Class
groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta. IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); // ARouter$$Group%%test 对象实例化 iGroupInstance.loadInto(Warehouse.routes); Warehouse.groupsIndex.remove(postcard.getGroup()); completion(postcard); // Reload 进入 else 分支} else { ... // postcadrd 设置属性如:destination,type,priority等 switch(routeMeta.getType) { case PROVIDER: Class
providerMeta = (Class
) routeMeta.getDestination(); IProvider instance = Warehouse.providers.get(providerMeta); if(instance == null) { IProvider povider = providerMeta.getConstructor().newInstance(); provider.init(mContext); // init 方法只在创建 provider 对象后调用一次, Warehose.providers.put(providerMeta, provider); // 存入 map,除非调用 ARouter#destory, 该 provider 对象会一直存在 map 中 postcard.setProvider(instance); postcard.greenChannel(); } break; case FRAGMENT: ... break; }}复制代码

到这里面 Warehose 中的 routesproviders也被我们放入了相应信息

_navigation()方法:

switch(postcard.getType()) {    case ACTIVITY:        ... // build intent        startActivity(requestCode, currentCotext, intent, postcard, callback);        break;    case POSTCARD:        return postcard.getProvider();    case Fragment:        Fragment fragment = fragmentMeta.getConstructor().newInstance();        fragment.setArguments(postcard.getExtras());        return fragment;    default:        return null;}return null;复制代码

接下来分析 ARroute#inject(Object)方法:

AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation()); autowiredService.autowire(objec);复制代码

代码很简单,关键是 AutowiredService 的实现

@Override    public void autowire(Object instance) {        String className = instance.getClass().getName();        try {            if (!blackList.contains(className)) {                ISyringe autowiredHelper = classCache.get(className);                if (null == autowiredHelper) {  // No cache.                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); // $$ARouter$$Autowired = $$ARouter$$Autowired                }                autowiredHelper.inject(instance); // Text1Activity$$ARouter$$Autowired.inject(this) 取出 bundle 中传递的参数                classCache.put(className, autowiredHelper);            }        } catch (Exception ex) {            blackList.add(className);    // This instance need not autowired.        }    }复制代码

到这里,所有的注解生成的 class 的作用及其实现方法的调用时机都已经介绍到了,相信读者应该对 ARouter 有了新的了解了吧。接下来讲一下我们平时比较少用到的 interceptor

ARouter官方为我们提供了一个拦截器的使用场景:

比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查复制代码

还记得在讲解 ARroute#navigation时我们跳过了拦截器相关的内容吗

if(!postcard.isGreenChannel()) {    // 拦截器相关,暂时跳过    interceptorSevice.doInterceptions(post, new InterceptorCallback() {        @Override        public void onContinue(Postcard postcard) {            _navigation(context, postcard, requestCode, callback);        }    })}复制代码

只要是拦截器,一般都会使用责任链设计模式,在 ARouter中也不例外,我们先看 interceptorService的创建过程,它的 type 是 Provider,在创建后会首先调用 init方法

@Overridepublic void init(final Context context) {    LogisticsCenter.executor.execute(new Runnable() {            @Override            public void run() {                for(entry : Warehose.interceptorsIndex) {                    Class
interceptorClass = entry.getValue(); IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance(); iInterceptor.init(context); Warehose.interceptors.add(iInterceptor); } } }}复制代码

interceptorService#doInterceptions 调用 _excute 方法

private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {        if (index < Warehouse.interceptors.size()) {            IInterceptor iInterceptor = Warehouse.interceptors.get(index);            iInterceptor.process(postcard, new InterceptorCallback() {                @Override                public void onContinue(Postcard postcard) {                    // Last interceptor excute over with no exception.                    counter.countDown();                    _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.                }                @Override                public void onInterrupt(Throwable exception) {                    // Last interceptor excute over with fatal exception.                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.                    counter.cancel();                }            });        }复制代码

常见错误分析

  1. service 内存泄漏, 考虑以下代码:

    public class HelloServiceImpl implements HelloService {    private Context context;        @Override    public void init(Context context) {        this.context = context    }        @Override    public void say() {        Toast.makeText(context, R.string.hello, Toast.LENGTH_LONG).show();    }}复制代码

    之前我们分析源码的时候就已经讲过了,service 的实现类在初始化后会被放入Warehose.providers,后续通过调用ARouter.inject都是从Warehose.providers中取出。在上面的代码中如果 context 是一个 activity 对象,将该 activity 赋值给了成员变量 context,则该 activity 对象不会被回收,造成了内存泄漏

  2. 混淆导致 service 为 null

    public class HelloHolder {     @Autowired(name = "/service/hello")    HelloService helloService;        public HelloHolder() {        ARouter.getInstance().inject(this);    }        public void sayHello() {        helloSerice.say;    }}复制代码

    该代码本身并没有问题,问题出在在 android 开发中我们需要混淆代码,大家还记得之前讲的 AutowiredService 的实现吧,关键代码在这里,

    if (null == autowiredHelper) {  // No cache.                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); // $$ARouter$$Autowired = $$ARouter$$Autowired                }复制代码

    本来 ARouter 构建的类名应该是 HelloHolder$$ARouter$$Autowired, 然后实例化该对象,调用该对象的 #inject方法,可是你现在将 HelloHolder混淆成了H,导致创建对象失败,这就是你为什么在代码中调用了 ARouter.getInstance().inject(this),但是 service 还是为 null 的原因

    在 android 开发中,Activity,Fragment 我们在混淆规则中是配置了 keep 等规则的,所以不会有问题。但是像上文中 HelloHolder 这种情况常常被我们所忽略,在开发中一定要注意,建议采用注解方式来保持类名不被混淆。

转载地址:http://puvdm.baihongyu.com/

你可能感兴趣的文章
很好的 .NET 换肤软件 IrisSkin
查看>>
(DT系列三)系统启动时, dts 是怎么被加载的
查看>>
PinYin Keyboard - PinYin Editor
查看>>
NSTimer invalidate后再isValid程序崩溃
查看>>
小心python标准库xml.etree.ElementTree 的find和findall方法
查看>>
现货黄金入门知识普及一:图形分析之K线理论
查看>>
Oracle备份还原表须知
查看>>
ubuntu常用快捷键
查看>>
asp.net mvc RouteCollection的RouteExistingFiles属性理解
查看>>
从程序员到项目经理(7):程序员加油站 -- 完美主义也是一种错
查看>>
创建一个自己的动态HTML-备
查看>>
iOS开发——网络编程OC篇&Socket编程
查看>>
逻辑回归
查看>>
asc.desc
查看>>
Java classes and class loading
查看>>
将多个文件夹内的txt合并
查看>>
Jquery — fancybox
查看>>
Linux shell笔记
查看>>
LINQ学习笔记(7) 标准查询运算符(上)
查看>>
分配虚拟化磁盘路径的优先级
查看>>