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

猜你喜欢

转载自blog.csdn.net/yangzhaomuma/article/details/107005105
今日推荐