组件化开发:
业务模块之间相互独立,互不依赖
startActivity的方式:
-
显式:
- 方式1:
intent.setClass(this,DemoActivity.class);
强依赖,也就是需要import class
,互不依赖的module则无法拿到class
对象 - 方式2:
intent.setClassName(this,"com.example.helloworld.DemoActivity");
- 方式1:
-
隐式:
- 方式3:
manifest: <intent-filter > <action android:name="com.action.DemoActivity"/>
intent.setAction("com.action.DemoActivity");
- 方式3:
针对方式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/...
文件夹路径下可以找到:
![](https://img.haomeiwen.com/i1419306/0cf8908444377b2f.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
时
![](https://img.haomeiwen.com/i1419306/26e066150d1b44c5.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利用字节码插桩来优化初始化时的性能
网友评论