今天开始架构师之路!分为6节课,以手写retofit ,Butterknife,Arount,Dagger2,hilit,ASM ,AOP为主
****APT :处理提取和处理 Annotation 的代码统称为(Annotation Processing Tool)。
作用
使用APT的优点就是方便、简单,可以少些很多重复的代码。
用过ButterKnife、Dagger、EventBus等注解框架的同学就能感受到,利用这些框架可以少些很多代码,只要写一些注解就可以了。
编写注解处理器
和运行时注解的解析不一样,编译时注解的解析需要我们自己去实现一个注解处理器。
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。而且这些生成的Java文件同咱们手动编写的Java源代码一样可以调用。(注意:不能修改已经存在的java文件代码)。
注解处理器所做的工作,就是在代码编译的过程中,找到我们指定的注解。然后让我们更加自己特定的逻辑做出相应的处理(通常是生成JAVA文件)。
注解处理器的写法有固定套路的,两步:
注册注解处理器(这个注解器就是我们第二步自定义的类)。
自定义注解处理器类继承AbstractProcessor。
如何看:java里面的jdk。会在resources的里面有META-INF。里面会有注解处理器的文件!javax.annotation.
所以apt用的是java的lib,不是Android !
注解处理器会在路径Build/classes/java/main/com/META-INF/Services/javax_anontation_process.
APT本质:生成java类
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
简单来说就是在编译期,通过注解生成.java文件。
详细解释: 它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),
APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
在这里我们将在每一个业务组件的 build.gradle 都引入ActivityRouter 的 Annotation处理器,我们将会在声明组件和Url的时候使用,annotationProcessor是Android官方提供的Annotation处理器插件,代码如下:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
}
[图片上传失败...(image-c4ef24-1640265031541)]
JavaPoet
更好的方案:通过javapoet
可以更加简单得生成这样的Java代码。(后面会说到)
JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件
需要添加JavaPoet的依赖
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">implementation'com.squareup:javapoet:1.9.0'</pre>
javapoet的详细内容:
1.方法名
2.返回值
3.打印
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("S)", System.class, "Hello, JavaPoet!")
.build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build(); javaFile.writeTo(System.out);</pre>
生成的代码
上面的功能一直在完成一件事情,那就是生成Java代码,那么生成的代码在哪?
在app/build/generated/source/apt中可以找到生成的Java文件
比如:MainActivity$$ARounter
autoService:主要作用:注册,****相当于安卓的四大组件需要注册一样!
google auto 中的autoservice 可以帮助我们生成对应的配置:使用AutoService注解,可以自动生成meta信息
spi 是一种服务发现的标准,对于开发中我们通常需要编写 META-INF/services 文件夹中定义的类。
自定义注解处理器注册才能被Java虚拟机调用,在上面的博客第四小节中用的方法是手动注册,这比较违反程序员懒的特点,在里面也提到了自动注册的方法,就是AutoService
介绍下依赖库auto-service
在使用注解处理器需要先声明,步骤:
1、需要在 processors 库的 main 目录下新建 resources 资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;)
这样声明下来也太麻烦了?这就是用引入auto-service的原因。
通过auto-service中的@AutoService可以自动生成AutoService注解处理器是Google开发的,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的
怎么让jvm在编译期间调用我们自己写的这个注解处理器呢?
有一个快捷办法就是使用谷歌的开源库auto,然后使用它提供的AutoService注解来实现,
另外一种办法就是自己手动去创建指定的文件夹,然后配置我们的注解处理器的路径。
注解处理器的debug调试
注解处理器的debug 跟普通的代码debug 有点不同:
在当前工程路径下输入命令
gradlew --no-daemon
-Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac
并在Edit Configurations 中新添加一个远程配置(remote),名字随意,端口为
5005。然后点击debug 按钮,就可以连接上远程调试器进行Annotation 的调试了。
demo地址:
https://github.com/jaminchanks/AnnotationProcess-Demo
参考博客:
https://www.jianshu.com/p/7af58e8e3e18
https://www.jianshu.com/p/6955a56844d7
点击世界是否会用到动态代理???---另外一种方案!
https://blog.csdn.net/u010008118/article/details/100896148
ButterKnife:黄油刀,被废弃了。现在已经被viewBind和databing取代,依赖注入框架。但是原理很重要!
问题:他和Arounter结合的时候会又什么问题?
R2?为什么会产生这样的问题?
编****译时注解实战: 手写 ButterKnife
4个模块
1.APP :使用
2.annoations:用于定义接口
3.annotation_complier:注解处理器,用于自动生成文件,注解处理器要放在java library里面
annotation_complier继承:abstractProcessor
4. lib :通过反射调用
重写几个方法
然后我们还需要重写AbstractProcessor
类
getSupportedAnnotationTypes()
方法
getSupportedSourceVersion()
方法
getSupportedAnnotationTypes()
方法用来指定该注解处理器是用来处理哪个注解的
getSupportedSourceVersion()
方法用来指定java版本,一般给值为SourceVersion.latestSupported()
。
注意:如果必要的方法没有写,会导致不执行主要的处理方法
里面重写的方法可以用注解代替, evenbus源码就是这么写的
那我们开始:创建项目,4步搞定
创建Android Module命名为app
创建Java library Module命名为 apt-annotation
创建Java library Module命名为 apt-processor 依赖 apt-annotation
创建Android library Module 命名为apt-library依赖 apt-annotation、auto-service
分析:
1.看下使用流程:1.有一个注解 2.有自动生成的类可以自动注入!
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;"> @BindView(R.id.tv_bangding)
TextView btn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this); //自动生成的类有一个bind方法,进行动态注入
}
}</pre>
手写步骤:
1.新建java module。把注解方进来。
为什么要把注解单独放一个module?
因为主module和编译module都需要用!
那为什么不把注解放在自动生成注解的module里面?
field注解+编译时+java module
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value(); }
</pre>
apt-annotation(自定义注解)
创建注解类BindView
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS)
:表示编译时注解
@Target(ElementType.FIELD)
:表示注解范围为类成员(构造方法、方法、成员变量)
@Retention: 定义被保留的时间长短
RetentionPoicy.SOURCE、RetentionPoicy.CLASS、RetentionPoicy.RUNTIME
@Target: 定义所修饰的对象范围
TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE等
这里定义了运行时注解BindView
,其中value()
用于获取对应View
的id
。
2.新建java module,把注解处理器需要的写进来,依赖javapoat和autoservice
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation 'com.squareup:javapoet:1.10.0' implementation project(':apt-annotation')</pre>
需要先模拟好生成的类,在主程序的build/generated/source/apt
[图片上传失败...(image-fd1f62-1640265031545)]
在自动生成模块里面会生成注册器
[图片上传失败...(image-15d481-1640265031545)]
一共5个方法:
其他3个方法可以用注解的形式下。
然后init方法和process方法注解处理器解析:
1.得到所以有标记的注解元素或者节点
2.得到包名
3.得到类名
4.拼接类
5.生成java文件
主要的一些对象:
1.元素
2.文件创造器
3.日志信息打印输出messager
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Messager mMessager; // 打印
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); /***
-
初始化一些类:比如日志,读写流 * @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}/***
-
需要处理注解的类型 * @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}/***
-
java的版本 * @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的带指定的注解BindView
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
//elements的信息保存到mProxyMap中,key:类全名
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);//通过元素得到注解类
int id = bindAnnotation.value();//得到注解类里面的值
proxy.putElement(id, variableElement);
}
//拿到开始map存放的信息
//通过javapoet生成java类 for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true; }
}</pre>
<pre style="margin: 8px 0px;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">/**
- 加入Method * javapoet */ private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host"); for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id); //VariableElement就是相应的元素
String name = element.getSimpleName().toString(); //控件名字
String type = element.asType().toString();//控件的类型,是btn还是text
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build(); }</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">最重要的是要得到VariableElement</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">主要就是从Elements
、TypeElement
得到想要的一些信息,如package name、Activity名、变量类型、id等,</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace;">使用的时候:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton;</pre>
</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">获取和解析控件:</pre>
<pre style="margin: 8px 0px;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">VariableElement element = mVariableElementMap.get(id); //VariableElement就是相应的元素 String name = element.getSimpleName().toString(); //控件名字 String type = element.asType().toString();//控件的类型,是btn还是text</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43);">常用的几个类(打印,元素,javapoat文件读写操作JavaFile)</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43);"> <pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">public class BindViewProcessor extends AbstractProcessor {
private Messager mMessager; // 打印
private Elements mElementUtils; // 对应的元素
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); //把带注解的元素封装在map中</pre>
3、封装一个调用module。 </pre>
<pre style="margin: 8px 0px;"> #### apt-library 工具类在BindViewProcessor
中创建了对应的xxxActivity_ViewBinding.java
,我们改怎么调用?当然是反射啦!!!apt-library 工具类
为什么用通过反射调用?
因为这些类都是动态生成的,根据不同的activity动态生成的!
</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">public class BindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">4.使用编译时注解</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace;">public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTools.bind(this);
mTextView.setText("bind TextView success");
mButton.setText("bind Button success");
}
}</pre>
</pre>
</pre>
</pre>
网友评论