编译时注解ButterKnife

先从SPI谈起

SPI: Service Provider Interfaces即Service提供者接口.
ServiceLoader类是在java上的SPI实现,使用时在java工程resources资源目录下创建META-INF/services文件夹,在services文件夹中创建以接口全名命名的文件,文件内容为该接口实现类全名,基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定,方便快捷。举个栗子:

//形状接口
public interface Shape {
    String introduce(); //介绍
}

//实现类一
public class Circle implements Shape {
    public String introduce() {
        return "圆形";  //言简意赅的介绍
    }
}

//实现类二
public class Sequare implements Shape {
    static{
        System.out.println("【Sequare】据说有延时加载,try it..");
    }
    public String introduce() {
        return "方形";
    }
}

文件位置

- src
    -main
        -resources
            - META-INF
                - services
                    - xxxpackage.Shape

文件名:包名.接口名
文件内容:包名.接口实现类,换行符分隔

xxxpackage.Circle
xxxpackage.Sequare

使用

ServiceLoader shapeLoader = ServiceLoader.load(Shape.class);
Iterator it = shapeLoader.iterator();
while(it.hasNext()){
    System.out.println("Iterator next()方法调用..");
    Shape shape = it.next();
    System.out.printf("what's shape?%s\n",shape.introduce());
}

这不是动态代理吗???再想想,跟Spring框架的IOC控制反转是否一样?Spring容器通过xml配置方式实例化接口实现类对象,如出一辙.

AutoService

Android上的SPI实现.这是一个注解处理器,是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。

compile 'com.google.auto.service:auto-service:1.0-rc3'

AutoService会自动在META-INF文件夹下生成Processor配置信息文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

APT技术

APT(Annotation Process Tool),是一种在代码编译时处理注解,按照一定的规则,生成相应的java文件,多用于对自定义注解的处理,目前比较流行的Dagger2, ButterKnife, EventBus3都是采用APT技术,对运行时的性能影响很小。

这里需要强调的是,ButterKnife之所以比xutils高效,是因为xutils使用了反射的方式去绑定View,而APT(编译时注解)比反射的性能要高

分析ButterKnife架构

共有三部分构成

  1. butterknife-annotations(java工程,注解)
  2. butterknife-compiler(java工程,用于生成代码)
  3. butterknife(Android工程,实例化XX_ViewBinding对象)

代码

  • butterknife-annotations
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
  • butterknife-compiler
//Android上的SPI实现.这是一个注解处理器,是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    private Filer mFiler;
    private Elements mElementUtils;

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

    // 1. 指定处理的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    // 2. 给到需要处理的注解
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        // System.out.println("------------------------>");
        
        // process 方法代表的是,有注解就都会进来 
        Set elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        /*for (Element element : elements) {
            //有注解的属性所在的activity
            Element enclosingElement = element.getEnclosingElement();
                                                           //有注解的属性名
            System.out.println("------------------------>"+element.getSimpleName().toString()+" "+enclosingElement.getSimpleName().toString());
        }*/
        // 解析 属性 activity -> List
        Map> elementsMap = new LinkedHashMap<>();
        for (Element element : elements) {
            //有注解的属性所在的activity
            Element enclosingElement = element.getEnclosingElement();
//有        //activity中有注解的属性集合
            List viewBindElements = elementsMap.get(enclosingElement);
            if (viewBindElements == null) {
                viewBindElements = new ArrayList<>();
                elementsMap.put(enclosingElement, viewBindElements);
            }

            viewBindElements.add(element);
        }

        // 生成代码
        for (Map.Entry> entry : elementsMap.entrySet()) {
            Element enclosingElement = entry.getKey();
            List viewBindElements = entry.getValue();

            // public final class xxxActivity_ViewBinding implements Unbinder
            String activityClassNameStr = enclosingElement.getSimpleName().toString();
            ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
            ClassName unbinderClassName = ClassName.get("com.justin.butterknife","Unbinder");
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr+"_ViewBinding")
                    .addModifiers(Modifier.FINAL,Modifier.PUBLIC).addSuperinterface(unbinderClassName)
                    .addField(activityClassName,"target",Modifier.PRIVATE);


            // 实现 unbind 方法
            // android.support.annotation.CallSuper
            ClassName callSuperClassName = ClassName.get("android.support.annotation","CallSuper");
            MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                    .addAnnotation(callSuperClassName);

            unbindMethodBuilder.addStatement("$T target = this.target",activityClassName);
            unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");");

            // 构造函数
            MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(activityClassName,"target");
            // this.target = target;
            constructorMethodBuilder.addStatement("this.target = target");
            // findViewById 属性
            for (Element viewBindElement : viewBindElements) {
                // target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
                // target.textView1 = Utils.findViewById(source, R.id.tv1);
                String filedName = viewBindElement.getSimpleName().toString();
                ClassName utilsClassName = ClassName.get("com.justin.butterknife","Utils");
                int resId = viewBindElement.getAnnotation(BindView.class).value();
                constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)",filedName,utilsClassName,resId);
                // target.textView1 = null;
                unbindMethodBuilder.addStatement("target.$L = null",filedName);
            }


            classBuilder.addMethod(unbindMethodBuilder.build());
            classBuilder.addMethod(constructorMethodBuilder.build());

            // 生成类,看下效果
            try {
                //获取包名
                String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();

                JavaFile.builder(packageName,classBuilder.build())
                        .addFileComment("butterknife 自动生成")
                        .build().writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("异常!");
            }
        }
        return false;
    }
}

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    //Android上的SPI实现.这是一个注解处理器,是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    //动态生成java代码
    implementation 'com.squareup:javapoet:1.9.0'
    implementation project(':butterknife-annotations')
}

tasks.withType(JavaCompile){
    options.encoding='UTF-8'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
  • butterknife
/**
 * 实例化xxxActivity_ViewBinding
 */
public class ButterKnife {
    public static Unbinder bind(Activity activity) {
        // xxxActivity_ViewBinding viewBinding = new xxxActivity_ViewBinding(this);
        try {
            Class bindClassName = (Class)
                    Class.forName(activity.getClass().getName() + "_ViewBinding");
            // 构造函数
            Constructor bindConstructor = bindClassName.getDeclaredConstructor(activity.getClass());
            Unbinder unbinder = bindConstructor.newInstance(activity);
            // 返回 Unbinder
            return unbinder;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return Unbinder.EMPTY;
    }
}

public interface Unbinder {
    @UiThread
    void unbind();

    Unbinder EMPTY = new Unbinder() {
        @Override
        public void unbind() {
        }
    };
}

public class Utils {
    public static  T findViewById(Activity activity,int viewId){
        return (T) activity.findViewById(viewId);
    }
}

使用

implementation project(':butterknife-annotations')
annotationProcessor project(':butterknife-compiler')
implementation project(path: ':butterknife')


public class MainActivity extends AppCompatActivity {
    
    @BindView(R.id.tv)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        textView.setText("5555555555555555");
    }
}

代码生成在主工程app\build\generated\source\apt\debug\com\justin\utils目录下

public final class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  public MainActivity_ViewBinding(MainActivity target) {
    this.target = target;
    target.textView = Utils.findViewById(target, 2131165325);
  }

  @Override
  @CallSuper
  public final void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");;
    target.textView = null;
  }
}

在 classpath 'com.android.tools.build:gradle:3.4.0'下,始终无法生成代码, public boolean process(Set set, RoundEnvironment roundEnvironment) 方法进不去,不知道为何?

你可能感兴趣的:(编译时注解ButterKnife)