编译时注解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<Shape> shapeLoader = ServiceLoader.load(Shape.class);
Iterator<Shape> it = shapeLoader.iterator();
while(it.hasNext()){
    System.out.println("Iterator<Shape> 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<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // System.out.println("------------------------>");
        
        // process 方法代表的是,有注解就都会进来 
        Set<? extends Element> 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<Element>
        Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
        for (Element element : elements) {
            //有注解的属性所在的activity
            Element enclosingElement = element.getEnclosingElement();
//有        //activity中有注解的属性集合
            List<Element> viewBindElements = elementsMap.get(enclosingElement);
            if (viewBindElements == null) {
                viewBindElements = new ArrayList<>();
                elementsMap.put(enclosingElement, viewBindElements);
            }

            viewBindElements.add(element);
        }

        // 生成代码
        for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
            Element enclosingElement = entry.getKey();
            List<Element> 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<? extends Unbinder> bindClassName = (Class<? extends Unbinder>)
                    Class.forName(activity.getClass().getName() + "_ViewBinding");
            // 构造函数
            Constructor<? extends Unbinder> 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 extends View> 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<? extends TypeElement> set, RoundEnvironment roundEnvironment) 方法进不去,不知道为何?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容