Arouter原理及源码分析

2023-03-28  本文已影响0人  gaom明

组件化开发:

业务模块之间相互独立,互不依赖

startActivity的方式:

  1. 显式
    • 方式1:
      intent.setClass(this,DemoActivity.class);
      强依赖,也就是需要import class,互不依赖的module则无法拿到class对象
    • 方式2:
      intent.setClassName(this,"com.example.helloworld.DemoActivity");
  2. 隐式
    • 方式3:
      manifest: <intent-filter > <action android:name="com.action.DemoActivity"/>
      intent.setAction("com.action.DemoActivity");

针对方式2和3,我们可以在basemodule中通过静态配置activity包名加类名或action进行夸module跳转。
针对方式1,互不依赖的module间如何拿到class对象,

一种思路可以通过反射:

intent.setClass(this, Class.forName("com.example.helloworld.DemoActivity"));
然后也是用静态配置方式管理。

另一种思路,就是利用apt代替反射

在编译期提前拿到你要反射的元素信息,这个元素就是注解声明的元素,可以是类,方法,属性,arouter就是使用这个思路,而其他采用apt的框架同理,包括butterknife,eventbus


什么是 APT ?

APT 全称 Annotation Processing Tool,翻译过来即注解处理器。引用官方一段对 APT 的介绍:APT 是一种处理注释的工具, 它对源代码文件进行检测找出其中的注解,并使用注解进行额外的处理。

如何使用:

https://www.jianshu.com/p/b616a569c516

简单流程:

新建Java lib,新建类继承AbstractProcessor类并给类上面加上@AutoService进行注解处理器的自动注册,重写getSupportedAnnotationTypes返回你需要过滤的自定义的注解,重写process来处理遍历的所有注解,获取注解的元素的所有信息并按自己需求创建java文件生成java源码,对于java代码的生成存在有多种方式,如字符串拼接,JavaPoet等。

以butterknife举例:

除了引用源码之外,还需要使用annotationProcessor 额外引用它的注解处理器

implementation 'com.jakewharton:butterknife:10.1.0'  
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'

使用时:

 @BindView(R.id.view)
 TextView view;

然后编译期通过它的注解处理器生成的新的java源码如下:

public class TestActivity_ViewBinding {
  public void bind(AnnotationTestActivity host) {
    host.view= (android.widget.TextView)(((android.app.Activity)host).findViewById( 2131231102));
  }
}

而运行期在onCreate中 ButterKnife.bind(this);
bind方法内部实现原理简单来说就是反射生成TestActivity_ViewBinding对象,并调用它的bind方法,即可将findViewById的结果赋值给被注解的元素上。


再看arouter:

同样需要引入依赖注解处理器

 compile 'com.alibaba:arouter-api:1.4.0'
 annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'

使用时:

@Route(path = "/test/activity4") //path应该放在basemodule中统一管理
public class SecondActivity 

然后编译期通过它的注解处理器生成的新的java源码文件在build/generated/...文件夹路径下可以找到:

image.png
public class ARouterGrouptest implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, SecondActivity .class, 
                                 "/test/activity4", "test", null, -1, -2147483648));
  }
}

然后在Application 中初始化 ARouter.init(this); 这时开始加载由注解处理器生成的ARouterGrouptest类对象,并访问loadInto方法进行路由表的收集工作,放入Map<String, Calss<>>中存储在ARouter中。

navigation

ARouter.getInstance()
        .build(path)//路径
        .withString("name ","value") //参数
        .navigation();

此时就是查询路由表,拿到class对象即可。

Autowired

而注解 @Autowired自动装载功能,类似ButterKnife

@Autowired
String name = "value";

onCreate中调用ARouter.getInstance().inject(this);即可完成参数的装载,此功能类似ButterKnife.bind(this);

Provider

ARouter的服务功能(Provider):就是module对外提供接口供其他module访问的:
base模块中声明一个公共接口协议,任何模块都可以访问:

public interface AccountService extends IProvider {
    Account getAccount();
}

在account中实现这个接口:

public class AccountServiceImpl implements AccountService {
    @Override
    public Account getAccount() {
        return AccountManager.getInstance().getAccount();
    }
} 

与activity的路由功能一样,也是通过注册和收集对应的class对象,来完成跨module的访问,只不过路由拿的是activity,而服务拿的是IProvider ,arouter内部拿到的就是AccountServiceImpl实例,但是会返给我们AccountService 也就是他的父类,客户端并不关心实现,所以拿到父类定义的接口协议就可以按需访问了。

字节码插桩

最后补充arouter运用了字节码插桩能力,在哪用呢,回顾一下,arouter通过注解处理器生成的新的java源码文件,然后在Application 中初始化ARouter.init(this);这时arouter需要查找每个module生成的新的java源码文件,通过查找dex文件遍历class查找这些类,找到后在new他们的实例,然后调用loadInto方法进行路由表的收集工作,这个环节是在Application中初始化的,故会影响启动时长;

所以arouter提供了字节码插桩功能,在编译期检索这些apt生成的java类,并把调用他们的代码插入到arouter的初始化的方法中,和apt一样,字节码插桩也是在编译期处理,不一样的是apt是在java编译class时,而字节码插桩在class转dex

image.png

关于字节码插桩自行搜索:asm+transform
arouter使用
根目录的buildgradel引入:

classpath "com.alibaba:arouter-register:?"

module中buildgradle使用:

apply plugin: 'com.alibaba.arouter'

arouter插桩后的代码位置和内容如下:(如果不用插件,则loadRouterMap方法就是空的,那么就在运行期初始化时遍历dex查找)

private static void loadRouterMap() {
    register("com.alibaba.android.arouter.routes.ARouterRootmodulea");
    register("com.alibaba.android.arouter.routes.ARouterRootmoduleb");
    register("com.alibaba.android.arouter.routes.ARouterProvidersmodulea");
  }

这里的register方法实际就是:

Class<?> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();

而后再调用obj.loadInto(Map);//Map就是路由表

总结:

1.startActivity的三种方式
2.apt代替反射的思路解决问题
3.butterknife原理
4.ARouter初始化做了什么
5.ARouter参数自动装载原理
6.ARouter的provider使用和原理
7.asm+transform字节码插桩技术
8.ARouter利用字节码插桩来优化初始化时的性能

上一篇 下一篇

猜你喜欢

热点阅读