Java学习之注解(五)Android循序渐进实现高逼格自定义ViewBinder

前言

Butterknife的代码到目前为止还没有仔细去看,这里也是自己在网上找的一个资料,主要是针对注解学习理解,但是发现这个学习资料估计是在Butterknife里面扣的,因为如果单单实现一个ViewBinder绑定注解,没必要那样写,代码高逼格了,既然看了这个代码,我就又实现到扩展循序渐进搞一遍,也来个可扩展的高逼格代码。

所以,有些东西没必要上路就想太多(兼容这,兼容那),先实现,在想着扩展。

参考文献:Android 注解使用之通过 annotationProcessor 注解生成代码实现自己的 ButterKnife 框架

正文

先看下目錄
在这里插入图片描述
以上分为4各模块:

1.app:宿主,测试注解,

2.annotation:存放自定义注解,

3.api:(类似“用于申明UI注解框架的api”网上的说法,不敢苟同,我自己理解下:)对自定义注解一种辅助操作模块,注解实现注入(api模块核心,不理解没关系,往下看你会深入理解的)。例如activity和Fragment,View使用方法有区别,在这个模块中处理,又例如资源管理:绑定和清除(可通过下面代码自己理解下)

高质量代码都体现在api模块下,其他的都是很难动的代码

4.compiler:用于在编译期间通过反射机制自动生成代码

额?有人问?为啥不可以使用同一个模块一次性完成!!!

  • compiler里面的代码只是在编译生成代码是有效,分开的这个compiler模块是不会被打包进入我们的apk的,从而减少apk体积;
  • 非常清晰明了,annotation就用于自定义注解,其他来了都不行;api用于辅佐使用注解
  • 据说butterknife就这么分的,还听说这种分发就是它传出来的,有些期待对它的学习了

总结:没啥好纠结的,这就好比你媳妇让你跪搓衣板,你偏偏犟“我就不,我就要跪键盘”一个道理

实现ViewBinder Demo V0.9版

先能实现就行,代码能不能直视,关上灯都一样!!!

1.)annotation 声明注解框架中要使用的注解

这里我声明了一个BindView注解,声明周期为RUNTIME,作用域为成员变量

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

因为这里仅仅想要实现绑定View控件,这里就自定义了一个BindView注解

注意啦:这里一定要写成RetentionPolicy.RUNTIME,而不是RetentionPolicy.CLASS,为啥?后面会介绍为啥不能用RetentionPolicy.CLASS

app.annotation module为java library,build.gradle配置如下

apply plugin: 'java'
sourceCompatibility =JavaVersion.VERSION_1_7
targetCompatibility =JavaVersion.VERSION_1_7
dependencies {
   
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

2.)api 一个帮助注解使用的类

package com.example.api;

import android.app.Activity;
import android.util.Log;
import android.view.View;

import com.example.annotation.BindView;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;

/**
 * Copyright (C), 2019-2020, 佛生
 * FileName: ViewInject
 * Author: 佛学徒
 * Date: 2020/11/10 13:29
 * Description:view绑定在activity上
 * History:
 */
public class ViewBindWithActivity {
   

	//运行时解析注解 BindView
    public static void inject(Activity activity) {
   
        //获取当前activity下的所有字段
        Field[] fields = activity.getClass().getDeclaredFields();

        //通过该方法设置所有的字段都可访问,否则即使是反射,也不能访问private修饰的字段
        AccessibleObject.setAccessible(fields, true);

        for (Field field : fields) {
   
            //这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效
            boolean needInject = field.isAnnotationPresent(BindView.class);

            if (needInject) {
   
                BindView anno = field.getAnnotation(BindView.class);

                int id = anno.value();
                if (id == -1) {
   
                    continue;
                }

                //以下相当于做了这个操作: textView = this.findViewById(R.id.tv_text);
                View view = activity.findViewById(id);
                Class fieldType = field.getType();
                try {
   
                    //把View转换成field声明的类型
                    field.set(activity, fieldType.cast(view));
                } catch (Exception e) {
   
                    Log.e(BindView.class.getSimpleName(), e.getMessage());
                }
            }
        }
    }
}

**为啥annotation 中BindView 只能使用RetentionPolicy.RUNTIME?**因为我以上代码采用的是java的反射机制,运行时注解及有效,那么必须RetentionPolicy.RUNTIME修饰

 //这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效
boolean needInject = field.isAnnotationPresent(BindView.class);

api引入的gradle文件

apply plugin: 'com.android.library'

...
dependencies {
   
   ...
    implementation project(path: ':annotation')
   
}

3.)app宿主:用于测试注解

package com.example.viewbinderdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.annotation.BindView;
import com.example.api.ViewBindWithActivity;

public class MainActivity extends AppCompatActivity {
   

    @BindView(R.id.tv_text)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//        textView = this.findViewById(R.id.tv_text);

        ViewBindWithActivity.inject(this);

        textView.setText("我是一个IT搬运工!!");
    }
}

gradle配置如下:

apply plugin: 'com.android.application'
...

dependencies {
   
   	...
    implementation project(path: ':annotation')
    implementation project(path: ':api')

}

以上即一个简单的通过反射实现的注解,啥都没有,你这么在项目里面写会被人揍成猪头!!!

**总结:**这个是通过反射机制完成的注解,很多原始的框架,例如EventBus3.0前,Dagger1等都是通过反射实现的,但是后来都没有这么去实现,因为效率问题,打个比方(希望没人叫比方),一个activity业务很重代码量多,这个时候用反射机制就很不友好了(去一个个找字段,方法,类,在代码量超多情况下执行效率非常低下,这里也让我们明白一个道理,没有啥事一蹴而就的,都是因为突出了问题或者一个新技术,再实现架构的迭代,这就是架构师的意思所在),那咋办,凉…当然有更好的方式去实现了

实现ViewBinder Demo V1.0版

android在编译时通过反射生成相应的代码

1.)annotation模块外甥打灯笼,但:

这里可以采用RetentionPolicy.CLASS修饰了

2.)complier根据注解在编译期间自动生成java代码

该类中主要2个类:1.一个是自定义AbstractProcessor类,android studio在编译时会自动扫描该文件;2.自定义类,主要用于编译执行AbstractProcessor时,生成我们所需要的java类。

注意,这里如果没有正确引入gradle,是不会执行自定义AbstractProcessor类,也可以说,如果在build中没有生成对应的类,那么除非build报错,否则必然是graldle引入问题

apply plugin: 'java-library'

dependencies {
   
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    //用于自动为 JAVA Processor 生成 META-INF 信息。
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    annotationProcessor "com.google.auto.service:auto-service:1.0-rc3"
    //快速生成.java文件的库
    implementation 'com.squareup:javapoet:1.8.0'
    implementation project(path: ':annotation')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

1.AnnotatedClass 类poet生成java代码

package com.example.compiler;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;

/**
 * Copyright (C), 2019-2020, 佛生
 * FileName: AnnotatedClass
 * Author: 佛学徒
 * Date: 2020/11/10 16:48
 * Description:生成代码的工具类
 * History:
 */
class AnnotatedClass {
   
    private String mBindingClassName;//生成新java类的名称
    private String mPackageName;//生成新java类包名

    private TypeElement mTypeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public AnnotatedClass(TypeElement classElement, Elements elementsUtils) {
   
        this.mTypeElement = classElement;
        PackageElement packageElement = elementsUtils.getPackageOf(mTypeElement);
        this.mPackageName = packageElement.getQualifiedName().toString();

        String className = mTypeElement.getSimpleName().toString();
        this.mBindingClassName = className + "$$ViewBinder";
    }

    public void putElement(int id, VariableElement element) {
   
        mVariableElementMap.put(id, element);
    }

    JavaFile generateFile() {
   //生成java文件
        JavaFile javaFile = JavaFile.builder(mPackageName, generateJavaCode()).build();

        return javaFile;
    }

    private TypeSpec generateJavaCode() {
   //生成java代码
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethod())
                .build();
        return bindingClass;
    }

    private MethodSpec generateMethod() {
   //生成方法,并且通过poet写入具体实现方法的代码
        ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());

        //加入一个bind方法,public类型,传入参数为 host(当前注解所在的类对象),void类型所以没有返回类型
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(host, "host");

        //bind方法的具体实现
        for (int id : mVariableElementMap.keySet()) {
   
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            //相当于加了host.view = (view类型)host.findViewById(id)
            methodBuilder.addCode("host." + name + " = " + "(" + type + ")host.findViewById( " + id + " );\n");
        }

        return methodBuilder.build();
    }
}

2.自定义AbstractProcessor类

package com.example.compiler;

import com.example.annotation.BindView;
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;


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

    private Filer mFiler;//文件相关的辅助类
    private Messager mMessager;//日志相关的辅助类
    private Elements mElementUtils;//元素相关类
    private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
   
        super.init(processingEnv);
        mMessager = processingEnv.getMessager();
        mElementUtils = processingEnv.getElementUtils();
        mFiler = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
   
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
   
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
   
        mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");

        mAnnotatedClassMap.clear();
        //获得被BindView注解标记的element
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //对不同的activity进行分类
        for (Element element: elements){
   
            VariableElement variableElement = (VariableElement) element;
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            String fullClassName = classElement.getQualifiedName().toString();
            AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);

            if(annotatedClass == null){
   
                annotatedClass = new AnnotatedClass(classElement,mElementUtils);
          
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值