2. Android 3分钟手写ButterKnife 彻底搞懂 注解处理器 APT 和IOC

今天开始架构师之路!分为6节课,以手写retofit ,Butterknife,Arount,Dagger2,hilit,ASM ,AOP为主

****APT :处理提取和处理 Annotation 的代码统称为(Annotation Processing Tool)

作用

使用APT的优点就是方便、简单,可以少些很多重复的代码。

用过ButterKnifeDaggerEventBus等注解框架的同学就能感受到,利用这些框架可以少些很多代码,只要写一些注解就可以了。

编写注解处理器

   和运行时注解的解析不一样,编译时注解的解析需要我们自己去实现一个注解处理器。

注解处理器(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的依赖

implementation'com.squareup:javapoet:1.9.0'

javapoet的详细内容:

1.方法名

2.返回值

3.打印

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);

生成的代码

上面的功能一直在完成一件事情,那就是生成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.有自动生成的类可以自动注入!

 @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方法,进行动态注入
}
}

手写步骤:

1.新建java module。把注解方进来。

为什么要把注解单独放一个module?

因为主module和编译module都需要用!

那为什么不把注解放在自动生成注解的module里面?

field注解+编译时+java module

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value(); }

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()用于获取对应Viewid

2.新建java module,把注解处理器需要的写进来,依赖javapoat和autoservice

implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation 'com.squareup:javapoet:1.10.0' implementation project(':apt-annotation')

需要先模拟好生成的类,在主程序的build/generated/source/apt

[图片上传失败...(image-fd1f62-1640265031545)]

在自动生成模块里面会生成注册器

[图片上传失败...(image-15d481-1640265031545)]

一共5个方法:

其他3个方法可以用注解的形式下。

然后init方法和process方法注解处理器解析:

1.得到所以有标记的注解元素或者节点

2.得到包名

3.得到类名

4.拼接类

5.生成java文件

主要的一些对象:

1.元素

2.文件创造器

3.日志信息打印输出messager

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

private Messager mMessager; // 打印

private Elements mElementUtils;
private Map 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 getSupportedAnnotationTypes() {
    HashSet supportTypes = new LinkedHashSet<>();
    supportTypes.add(BindView.class.getCanonicalName());
    return supportTypes;
    }

    /***

  • java的版本 * @return
    */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
    mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
    mProxyMap.clear();
    //得到所有的带指定的注解BindView
    Set 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; }

}

/**

  • 加入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(); }

最重要的是要得到VariableElement

主要就是从ElementsTypeElement得到想要的一些信息,如package name、Activity名、变量类型、id等,

使用的时候:
public class MainActivity extends AppCompatActivity {

@BindView(R.id.tv)
TextView mTextView;

@BindView(R.id.btn)
Button mButton;

获取和解析控件:

VariableElement element = mVariableElementMap.get(id); //VariableElement就是相应的元素 String name = element.getSimpleName().toString(); //控件名字 String type = element.asType().toString();//控件的类型,是btn还是text

常用的几个类(打印,元素,javapoat文件读写操作JavaFile)

 
public class BindViewProcessor extends AbstractProcessor {

private Messager mMessager; // 打印

private Elements mElementUtils; // 对应的元素
private Map mProxyMap = new HashMap<>(); //把带注解的元素封装在map中

3、封装一个调用module。

 #### apt-library 工具类在BindViewProcessor中创建了对应的xxxActivity_ViewBinding.java,我们改怎么调用?当然是反射啦!!!apt-library 工具类

为什么用通过反射调用?

因为这些类都是动态生成的,根据不同的activity动态生成的!

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();
}
}
}

4.使用编译时注解

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");
}
}

你可能感兴趣的:(2. Android 3分钟手写ButterKnife 彻底搞懂 注解处理器 APT 和IOC)