自己动手实现ButteKnife简介

本文介绍了如何实现一个类似于ButterKnife的注解处理器。首先,讲解了注解处理器的基本概念,它在编译时处理注解并生成Java代码。接着,展示了项目的模块结构,包括app、apt_annotation和apt_processor。在apt_annotation中定义自定义注解,在apt_processor中编写注解处理器。通过javapoet库生成Java代码,并使用auto-service注册注解处理器。最后,演示了注解处理器的使用步骤,以及可能出现的问题,如注解处理器注册错误会影响代码生成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ButterKnife我们都用过,很好用,今天我们自己实现一个ButterKnife。
博客全程参考

https://www.jianshu.com/p/cc8379522c5e。

一,注解处理器简介

注解处理器是(Annotation Processor)是Javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。你可以自己定义注解和注解处理器去搞一些事情。一个注解处理器它以Java代码或者(编译过的字节码)作为输入,生成文件(通常是java文件)。这些生成的java文件不能修改,并且会同其手动编写的java代码一样会被javac编译。大概的过程就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。

注解处理器继承自AbstractProcessor。固定写法如下:

public class BindViewProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return true;
    }
}
主要函数简介
init(ProcessingEnvironment processingEnv)参数ProcessingEnvironment 提供了Element,Filer,Messager等工具
getSupportedAnnotationTypes()指定支持的注解
getSupportedSourceVersion指定支持Java版本
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)是最主要的函数,在这里扫描和处理你的注解并生成Java代码

注解处理器是需要注册的,为了使用方便,我们使用Google提供的开源库进行处理:
implementation 'com.google.auto.service:auto-service:1.0-rc2'

@AutoService(Process.class)
public class BindViewProcessor extends AbstractProcessor {
	...
}

二,项目结构
在这里插入图片描述
app是android application
apt_annotation是JavaLibrary 存放自定义注解
apt_processor是JavaLibrary 存放注解处理器
app 依赖apt_annotation和apt_processor
apt_processor依赖apt_annotation
其中apt_processor 的 gradle如下
在这里插入图片描述
javapoet是用来生成Java代码的,我在前面的博客中有提到过。
auto-service则是注册注解处理器的。

三,自定义注解
在apt_annotation module中新建BindView.java文件,生成我们的注解。

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

四,注解处理器的使用

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

    private Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
    }

    //指定要处理的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> typeElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Map<TypeElement, Map<Integer, VariableElement>> elementMap = new HashMap<>();
        for (Element element : typeElements) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            Map<Integer, VariableElement> map = elementMap.get(typeElement);
            if (map == null) {
                map = new HashMap<>();
                elementMap.put(typeElement, map);
            }
            BindView bindView = variableElement.getAnnotation(BindView.class);
            int value = bindView.value();
            map.put(value, variableElement);
        }
        for (TypeElement typeElement : elementMap.keySet()) {
            Map<Integer, VariableElement> map = elementMap.get(typeElement);
            String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
            JavaFile javaFile = JavaFile.builder(packageName, generateClass(typeElement, map)).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    //生成一个类
    private TypeSpec generateClass(TypeElement typeElement, Map<Integer, VariableElement> map) {
        TypeSpec typeSpec = TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "ViewBinding")
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethod(typeElement, map))
                .build();
        return typeSpec;
    }

    //生成bind()方法
    private MethodSpec generateMethod(TypeElement typeElement, Map<Integer, VariableElement> map) {
        ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
        String parameter = "_" + toLowFirstChar(className.simpleName());

        MethodSpec.Builder builder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(className, parameter);
        for (int valueId : map.keySet()) {
            VariableElement element = map.get(valueId);
            String text = "{0}.{1} = ({2})({3}.findViewById({4}))"; //_mainActivity.btn_remove = (android.widget.Button) (_mainActivity.findViewById(2131165219));
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            builder.addStatement(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(valueId)));
        }
        return builder.build();
    }

    //将首字母转变为小写
    private String toLowFirstChar(String string) {
        if (string == null || string.length() == 0) {
            return "";
        }
        if (Character.isLowerCase(string.charAt(0))) {
            return string;
        }
        return String.valueOf(Character.toLowerCase(string.charAt(0))) + string.substring(1);
    }

ReBuild项目之后可以在generatedJava文件中看到自动生成的文件。
在这里插入图片描述
这个就是我们自动生成的java文件。
在这里插入图片描述
findViewById的绑定文件已经生成了,接下来只需要通过反射调用这个bind()方法就可以了。
先生成一个MyButterKnife.java文件·,内容如下:

public class MyButterknife {
    public static void bind(Activity activity) {
        Class clazz = activity.getClass();          //MainActivity
        try {
            Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding");   //MainActivityViewBinding
            Method method = bindViewClass.getMethod("bind", activity.getClass());
            method.invoke(bindViewClass.newInstance(), activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后在MainActivity里面bind一下,使用和ButterKnife一样,这样就完成了。

public class MainActivity extends AppCompatActivity {


    @BindView(R.id.tv_hint)
    TextView hintTv;

    @BindView(R.id.btn_content)
    Button contentBtn;

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

        hintTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "You click hinTv", Toast.LENGTH_SHORT).show();
            }
        });
        contentBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "You click contentBtn", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

五,一点问题

注解处理器是需要注册的,我们使用的是@AutoService(Processor.class)注册的,如果把Processor单词拼错了,会导致不能在generatedJava下生成文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值