手写ButterKnife

Posted lxn_李小牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写ButterKnife相关的知识,希望对你有一定的参考价值。

 

前言

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

结构

app:我们的android项目

butterknife-annotation:java library,定义注解

butterknife-compiler:java library,注解处理器

 

编译时报错

 

解决办法:

在annotation和compiler的gradle模块中,添加

tasks.withType(JavaCompile) 
    options.encoding = "UTF-8"

/**
 * 描述:用于绑定变量
 *
 * @author Create by zxy on 2018/5/10
 */
@Retention(RetentionPolicy.SOURCE) // 注解只在源码级别保留
@Target(ElementType.FIELD) // 注解用在字段上
public @interface BindView 
    int value();
/**
 * 描述:提供bind方法供生成的类调用
 *
 * @author Create by zxy on 2018/5/10
 */
public interface ViewBinder<T> 
    void bind(T target);

build.gradle文件

apply plugin: 'java-library'

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

tasks.withType(JavaCompile) 
    options.encoding = "UTF-8"

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

/**
 * 描述:相当于一个监视者,监控源文件中的注解
 *
 * @author Create by zxy on 2018/5/10
 */

@AutoService(Processor.class) // 注册注解处理器
public class ButterKnifeProcessor extends AbstractProcessor 
    //写文件的对象
    private Filer mFiler;
    private Elements mElementUtils;


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

    /**
     * 注解处理器支持的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() 
        return SourceVersion.latestSupported();
    

    /**
     * 注解处理器支持的注解名称
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() 
        Set<String> annotationTypes = new HashSet<>();
        annotationTypes.add(BindView.class.getCanonicalName());
        return annotationTypes;
    

    /**
     * @param roundEnv
     *
     * package practice.lxn.cn.androidpractice.pojo; // PackageElement
     *   public class Book implements Parcelable // TypeElement
     *      private int bookId; // VariableElement
     *      private String bookName; // VariableElement
     *      public int getBookId()  // ExecutableElement
     *          return bookId;
     *      
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
        // 获取所有包含BindView注解的元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        // 需要对不同Activity中的注解进行分类,因为Set集合中包含了所有Activity中的注解
        Map<String,List<VariableElement>> activityElementMap = new HashMap<>();
        for (Element element : elements) 
            VariableElement variableElement = (VariableElement) element;
            //获取当前元素对应的Activity名称
            String activityName = getActivityName(variableElement);
            List<VariableElement> elementList = activityElementMap.get(activityName);
            if (elementList == null) 
                elementList = new ArrayList<>();
                //将Activity名称和它对应的元素集合存放到一起
                activityElementMap.put(activityName,elementList);
            
            elementList.add(variableElement);
        

        // 开始产生Java文件
        for (String activityName : activityElementMap.keySet()) 
            // 获取Activity对应的带注解的成员
            List<VariableElement> elementList = activityElementMap.get(activityName);
            // 获取包名
            String packageName = getPackageName(elementList.get(0));
            // 获取最后生成的文件的名称package practice.lxn.cn.testapp.MainActivity_ViewBinder;
//            String viewBinderName = activityName + "_ViewBinder";
            /* 需要生成文件的格式
            package practice.lxn.cn.testapp;
            import practice.lxn.cn.testapp.ViewBinder
            public class MainActivity_ViewBinder implements ViewBinder<MainActivity> 
                @Override
                public void bind(MainActivity target) 
                    target.btn = (Button)target.findViewById(1231123423432);
                
            */
            //===========================================================================
           /* Writer writer;
            //MainActivity_ViewBinder
            String simpleName = elementList.get(0).getEnclosingElement().getSimpleName().toString() + "_ViewBinder";
            try 
                // 方式一:通过原生的JavaFileObject拼接
                JavaFileObject javaFileObject = mFiler.createSourceFile(viewBinderName);
                writer = javaFileObject.openWriter();
                writer.write("package " + packageName +";");
                writer.write("\\n");
                writer.write("import " + packageName + ".ViewBinder;");
                writer.write("\\n");
                writer.write("public class " + simpleName + " implements ViewBinder<" + activityName + "> ");
                writer.write("\\n");
                writer.write("public void bind(" + activityName + " target) ");
                writer.write("\\n");
                for (VariableElement element : elementList) 
                    String variableName = element.getSimpleName().toString();
                    TypeMirror typeMirror = element.asType();
                    int id = element.getAnnotation(BindView.class).value();
                    writer.write("target." + variableName + " = (" + typeMirror + ")target.findViewById(" + id + ");");
                    writer.write("\\n");
                    writer.write("");
                    writer.write("\\n");
                    writer.write("");
                  
                
             catch (IOException e) 
                e.printStackTrace();
            finally
                writer.close(); // 写完需要关闭
             */
            //方式二:通过JavaPoet提供的API
              /* 需要生成文件的格式
            package practice.lxn.cn.testapp;
            import practice.lxn.cn.testapp.ViewBinder
            public class MainActivity_ViewBinder implements ViewBinder<MainActivity> 
                @Override
                public void bind(MainActivity target) 
                    target.btn = (Button)target.findViewById(1231123423432);
                
            */
            String simpleName = elementList.get(0).getEnclosingElement().getSimpleName().toString();
            ClassName viewBinderName = ClassName.get(ViewBinder.class.getPackage().getName(), ViewBinder.class.getSimpleName());
            ClassName activityClassName = ClassName.bestGuess(simpleName);
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(activityClassName + "_ViewBinder")
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(ParameterizedTypeName.get(viewBinderName,activityClassName));
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(TypeVariableName.get(activityName),"target")
                    .returns(TypeName.VOID);
            for (VariableElement element : elementList) 
                String variableName = element.getSimpleName().toString();
                TypeMirror typeMirror = element.asType();
                int id = element.getAnnotation(BindView.class).value();
                methodBuilder.addStatement("target." + variableName +"= (" + typeMirror + ")" + "target.findViewById(" + id + ");");
            
            MethodSpec bind = methodBuilder.build();
            TypeSpec MainActivity_ViewBinder = typeBuilder.addMethod(bind).build();
            JavaFile javaFile = JavaFile.builder(packageName,MainActivity_ViewBinder)
                    .build();
            try 
                javaFile.writeTo(mFiler);
             catch (IOException e) 
                e.printStackTrace();
            
        
        return false;
    

    /**
     * 获取包名
     */
    private String getPackageName(VariableElement variableElement) 
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        PackageElement packageElement = mElementUtils.getPackageOf(typeElement);
        return packageElement.getQualifiedName().toString();
    

    /**
     * 获取Activity名称
     */
    private String getActivityName(VariableElement variableElement) 
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String packageName = getPackageName(variableElement);
        // package practice.lxn.cn.testapp.MainActivity
        return packageName + "." + typeElement.getSimpleName().toString();
    
apply plugin: 'java-library'

dependencies 
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation project(':butterknife-annotation')
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    // 动态生成Java源文件的API
    implementation 'com.squareup:javapoet:1.9.0'


tasks.withType(JavaCompile) 
    options.encoding = "UTF-8"

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

在ButterKnifeProcessor中,我们可以通过两种方式生成最终的源文件,通过原生的JavaFileObject或者JavaPoet,JavaPoet是square开源的一个项目,通过下面方式引入

 

implementation 'com.squareup:javapoet:1.9.0'

主项目中

最终生成的源文件

 

 

package practice.lxn.cn.testapp;

import practice.lxn.cn.butterknife_annotation.ViewBinder;

public class MainActivity_ViewBinder implements ViewBinder<MainActivity> 
  public void bind(practice.lxn.cn.testapp.MainActivity target) 
    target.btn= (android.widget.Button)target.findViewById(2131427445);;
  

 

 

 

 

 

 

 

 

 

 

 

以上是关于手写ButterKnife的主要内容,如果未能解决你的问题,请参考以下文章

动脑学院-手写ButterKnife框架(不包含自动生成代码)

手写ButterKnife来搞明白Android注解处理器

手写ButterKnife来搞明白Android注解处理器

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

手写网络访问框架

手写网络访问框架