框架手写系列---apt注解处理器方式实现ButterKnife框架

Posted 战国剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了框架手写系列---apt注解处理器方式实现ButterKnife框架相关的知识,希望对你有一定的参考价值。

一、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对于去重代码、生成代码、切面编程等,十分有效,多数涉及到框架的,都会用到该用法。

以上是关于框架手写系列---apt注解处理器方式实现ButterKnife框架的主要内容,如果未能解决你的问题,请参考以下文章

框架手写系列---通过反射手写EventBus框架

框架手写系列---AspectJ方式实现埋点上传框架

使用APT实现Android中View的注入

框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架

框架手写系列---Asm方式实现日志插入

源码分析-手写springMVC框架@RequestMapping和@Controller注解