框架手写系列---apt注解处理器方式实现butterknife框架(代码片段)

战国剑 战国剑     2022-12-05     162

关键词:

一、ButterKnife

ButterKnife作为常用框架之一,主要用于简化代码,减少重复代码。

这里主要注重原理与核心,将分步骤手写它的核心代码。

ButterKnife最常用的是去除代码中的findViewById,以注解的方式代替原有的代码,这里也从这里入手。

Android之注解的使用——绑定android控件 这是前文中通过反射方式实现,可对比查看

二、原理说明

public class MainActivity extends AppCompatActivity 

    @BindView(R.id.hello)
    TextView hello;

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

    ...

核心原理:

1、ButterKnife的用法,如上图所示,以注解的方式标识控件。

2、通过apt注解处理器,处理该注解BindView,将注解上的参数传入到处理器中,生成代码。

3、通过调用生成的代码,实现findviewById等。

三、手写实现

1、定义注解BindView

//编译时起效
@Retention(RetentionPolicy.CLASS)
//针对的是属性
@Target(ElementType.FIELD)
public @interface BindView 
    int value();

2、注解处理器的编写

//注解处理器的依赖,此处有注意点:
dependencies 
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //依赖注解
    implementation project(path: ':bind-annotation')
    //如果是3.6+的android studio,auto-service需要按如下依赖
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

//注册编译处理器到系统
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor 

    private Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) 
        super.init(processingEnvironment);
        //filter 用于后续写文件
        filer = processingEnvironment.getFiler();

    

    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 
        //roundEnvironment中根据annotation获取节点
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //区分上面获取到的节点,获取类名和该类下的BindView节点====> 形成一个Map<类名,BindView标记的View集合>这样的结构。
        //后续根据这个结构生成一个或者多个java文件
        Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
        Map<String,List<VariableElement>> map = new HashMap<>();
        List<VariableElement> variableElements;
        while (iterator.hasNext())
            //节点集合
            VariableElement variableElement = (VariableElement)iterator.next();

            //类名
            String className = variableElement.getEnclosingElement().getSimpleName().toString();
            variableElements = map.get(className);
            if(map.get(className) == null)
                variableElements = new ArrayList<>();
                map.put(className,variableElements);
            
            variableElements.add(variableElement);
        


        //写文件
        Writer writer = null;
        Iterator<String> iteratorNames = map.keySet().iterator();
        while (iteratorNames.hasNext())
            String currentClassName = iteratorNames.next();

            List<VariableElement> currentVariableElements = map.get(currentClassName);
            //包名
            String packageName = processingEnv.getElementUtils().getPackageOf(currentVariableElements.get(0)).toString();

            try 
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + currentClassName + "$$ViewBind");
                writer = sourceFile.openWriter();

                StringBuilder stringBuilder = new StringBuilder();

                stringBuilder.append("package "+packageName+";\\n");
                stringBuilder.append("import com.sunny.bind_api.IBindView;\\n");
                stringBuilder.append("public class "+currentClassName + "$$ViewBind implements IBindView<"+packageName+"."+currentClassName+">"+"\\n");
                stringBuilder.append("public void bind("+packageName+"."+currentClassName+" target)\\n");

                for(VariableElement currentElement :currentVariableElements)
                    //控件名字
                    String filedName = currentElement.getSimpleName().toString();
                    //控件类型
                    TypeMirror typeMirror = currentElement.asType();
                    //控件resId
                    int resourceId = currentElement.getAnnotation(BindView.class).value();

                    stringBuilder.append("target."+filedName +" = ("+typeMirror+")target.findViewById("+resourceId+");\\n");
                
                stringBuilder.append("\\n\\n");

                writer.write(stringBuilder.toString());
             catch (IOException e) 
                e.printStackTrace();
            finally 
                if(writer != null)
                    try 
                        writer.close();
                     catch (IOException e) 
                        e.printStackTrace();
                    
                
            
        
        return false;
    


    @Override
    public SourceVersion getSupportedSourceVersion() 
        return processingEnv.getSourceVersion();
    

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

这里的核心在于:如何获取到Field类型的注解控件,然后 获取该控件的类型,resourceId,并把以上控件信息,放置到一个集合中,后续遍历并生成文件。

1、variableElement代表属性节点

2、ExecutableElement代表方法节点

3、TypeElement代表最外层的类节点

此处在生成代码时,实现了一个接口:IBindView,主要是为了后续在调用代码时更有指向性与范围。

public interface IBindView<T> 
    void bind(T t);

3、调用代码的编写

public class ButterKnife 

    public static void bind(Activity activity)
        String className = activity.getClass().getName()+"$$ViewBind";
        try 
            Class<?> aClass = Class.forName(className);
            //IBindView的使用
            IBindView o = (IBindView)aClass.newInstance();
            //具体调用
            o.bind(activity);
         catch (Exception e) 
            e.printStackTrace();
        
    

目前在用的ButterKnife,复杂性与完整性,比以上的核心简写复杂很多,但本质原理就是上面描述的这样:用apt的方式,简化代码与去除重复代码。

apt对于去重代码、生成代码、切面编程等,十分有效,多数涉及到框架的,都会用到该用法。

框架手写系列---aspectj方式实现埋点上传框架(代码片段)

一、切面编程AOP、OOP是程序中经常涉及的概念。OOP--面向对象编程,java、c#等都是面向对象的编程语言,主张万物皆对象。AOP是一种编程思想,对OOP的有效补充,在具体代码中,可以针对任意方法、属性、类等... 查看详情

框架手写系列---javassist修改字节码方式,实现美团robust热修复框架(代码片段)

本文用javassist方式,模拟美团Robust插件的前置处理:用插入代码的方式,针对apk中的每个方法都插入一段静态代码判断语句,用于控制是否启用热修复fix(也就是动态加载patch包到原apk中)。一、插件的生... 查看详情

aop之注解处理器apt在android中的finderview实际详解(代码片段)

...     具体可查看注解原理详解。      2、通过注解处理器APT来获取。   这里使用的是方式二。注解器处理是在build编译时执行的二原理    1、定义Method、Field的注解类分别为OnClick、BindView,分别应用于Method和Field  ... 查看详情

框架手写系列---asm方式实现日志插入(代码片段)

asm与javassist作为修改字节码文件的两种常用方式,在编译过程中,可以动态修改class字节码文件。前文已用javassist方式实现热修复的前置步骤,本文用asm实现动态的给每个activity插入Log日志。一、插件的生成与依赖导... 查看详情

手写butterknife(代码片段)

...-annotation:javalibrary,定义注解butterknife-compiler:javalibrary,注解处理器 编译时报错 解决办法:在annotation和compiler的gradle模块中,添加tasks.withType(JavaCompile)options.encoding="UTF-8"/***描述:用于绑定变量**@authorCreatebyz... 查看详情

手写butterknife(代码片段)

 前言ButterKnife是一个依赖注入框架,8.0之前是通过反射的方式实现,具体实现可以参考这篇文章自定义注解,今天我们来看下8.0之后的编译时注解实现方式,编译时注解相比运行时注解效率高,是通过在编译... 查看详情

注解深入浅出(二apt)

...ool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省... 查看详情

注解处理器apt在java中的实现(代码片段)

...文全名(AnnotationProcessorTool),即:注解处理器。它是 javac 的一个工具,这是Sun为了帮助注解的处理过程而提供的工具,apt被设计为操作Java源文件,而不是编译后的类。作用阶段示意图如下:具... 查看详情

注解处理器(apt)是什么?(代码片段)

...解,这篇咱们科普一下注解的其中一种用途——注解处理器(APT),文章会手把手的帮助大家学会APT的使用,并使用简单的例子来进行练习。一、定义注解处理器(AnnotationProcessingTool,简称APT)ÿ... 查看详情

聊聊手写mybatis注解配置方式

...batis集成SpringXML方式聊聊、Mybatis集成Spring注解方式聊聊、手写MybatisXML配置方式 在《聊聊、手写MybatisXML配置方式》中聊了通过XML配置方式来实现Mybatis,也聊到了Mybatis中用到 查看详情

04注解处理器(apt)是什么?——《android打怪升级之旅》(代码片段)

...解,这篇咱们科普一下注解的其中一种用途——注解处理器(APT),文章会手把手的帮助大家学会APT的使用。一、定义注解处理器(AnnotationProcessingTool,简称APT),是JDK提供的工具,用于在编译... 查看详情

spring框架核心功能手写实现(代码片段)

文章目录概要Spring启动以及扫描流程实现基础环境搭建扫描逻辑实现bean创建的简单实现依赖注入实现BeanNameAware回调实现初始化机制模拟实现BeanPostProcessor模拟实现AOP模拟实现概要手写Spring启动以及扫描流程手写getBean流程手写Bean... 查看详情

eventbus手写实现事件通信框架(实现几个关键的封装类|消息中心|订阅注解|订阅方法封装|订阅对象-方法封装|线程模式)(代码片段)

文章目录一、消息中心二、订阅方法时的注解三、订阅方法封装四、订阅对象-方法封装五、线程模式一、消息中心此处暂时只实现一个单例类,后续注册订阅者,处理事件传递,取消注册订阅者,等功能在该单例类的基础上扩展;packag... 查看详情

javaspi06-自己从零手写实现spi框架

...pi05-dubboadaptiveextension自适应拓展spi06-自己从零手写实现SPI框架spi07-自动生成SPI配置文件实现方式回顾学习了java的SPI和dubbo的SPI实现之后,希望实现一个属于自己的SPI框架 查看详情

《java手写系列》-手写mybatis框架(代码片段)

文章目录1.自定义Executor执行器接口3.自定义BoundSql类3.自定义Executor执行器接口实现类SimpleExecutor4.补全DefaultSqlSession类中的接口实现方法5.测试全部查询6.测试条件查询7.测试新增8.测试新增9.测试更新10.测试删除在上一篇中已经对sq... 查看详情

《java手写系列》-手写mybatis框架(代码片段)

文章目录1.自定义SqlSessionFactory接口2.自定义SqlSession接口3.自定义DefaultSqlSession实现类4.自定义DefaultSqlSessionFactory5.自定义SqlSessionFactoryBuilder本章接着上一章手写MyBatis,主要这集中在sqlSession对象的获取!1.自定义SqlSessionFact 查看详情

spring系列之手写一个springmvc(代码片段)

...理及手动实现Spring系列之AOP的原理及手动实现Spring系列之手写注解与配置文件的解析引言在前面的几个章节中我们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能。这一节我们来实现一... 查看详情

aop之注解处理器apt在android中的finderview实际详解(代码片段)

一前言     android中现今流行的各大框架,比如ButterFly、eventBus、OrmLite、Retrofit等都使用注解,注解是什么呢?注解就是元数据,可以理解为属性、方法、类等的一个说明,具体详解可百度,也可移步我... 查看详情