【Android】注解框架(三)-- 编译时注解,手写ButterKnife

目录

  1. 【Android】注解框架(一)-- 基础知识Java 反射
  2. 【Android】注解框架(二)-- 基础知识(Java注解)& 运行时注解框架
  3. 【Android】注解框架(三)-- 编译时注解,手写ButterKnife
  4. 【Android】注解框架(四)-- 一行代码注入微信支付

apt介绍

作为Android程序员应该绝大部分分人都用过ButterKnife,Retrofit等框架,这些框架只需要在用的时候使用注解,就可以直接使用了,非常方便。并且这些框架并没有减少性能。

那么这些框架做了哪些东西呢?

我们以ButterKnife为例:

@BindView(R.id.textview)
TextView textview;

private Unbinder bind;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    bind = ButterKnife.bind(this);

    textview.setText("dfjslafjsalfls");
}

@Override
protected void onDestroy() {
    bind.unbind();
    super.onDestroy();
}

上面是我们平常使用的时候所使用的代码,这些代码到底做了什么呢?

  1. 我们给元素写上注解
  2. 在编译器执行的环节,系统收集我们所写注解的元素,并将这些元素组合成Java代码MainActivity_ViewBinding
  3. 当我们调用ButterKnife.bind的时候,通过动态注入的方式,将MainActivityMainActivity_ViewBinding关联起来,并给所有的注解所在的元素赋值。

而这些东西的核心就是在编译期间生成我们所需要的Java文件,这样我们在使用的时候就基本没有性能的影响。

网上找到的APT的流程图:

手写ButterKnife

通过上面的图我们先生成这些module

butterknife_流程图

app - 主项目
butterknife - android lib
annotaion - java lib
compiler - java lib

在这些module的配置文件中配置

  1. project的build.gradle

    // 如果你的gradle版本是3.0以上则不需要配置
    buildscript {
        
        repositories {
            google()
            jcenter()
            mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.0.0'
    
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        }
    }
    
    allprojects {
        repositories {
            google()
            jcenter()
            mavenCentral()
        }
    }
    
  2. butterknife-compiler的build.gradle

    添加auto-service和javapoet这两个lib是用来方便我们生成Java代码的。

    auto-service: https://github.com/google/auto
    javapoet: http://blog.csdn.net/crazy1235/article/details/51876192

    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        compile 'com.google.auto.service:auto-service:1.0-rc3'
        compile 'com.squareup:javapoet:1.8.0'
        implementation project(':butterknife-annotations')
    }
    
  3. app的build.gradle

    // gradle版本小于3.0
    apply plugin: 'com.neenbedankt.android-apt'
    
    compile project(':butterknife')
    compile project(':butterknife-annotations')
    apt project(':butterknife-compiler')
    
    // gradle版本大于3.0
    compile project(':butterknife')
    compile project(':butterknife-annotations')
    annotationProcessor project(':butterknife-compiler')
    

在annotation中写上注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}

在compiler中编写注解生成器

java文档: http://www.yq1012.com/api/index.html-overview-summary.html
javax.annotation.processing包
javax.lang.model.element包

  1. 创建类processor继承AbstractProcessor

    @AutoService(Processor.class)
    public class ButterKnifeProcessor extends AbstractProcessor {
        private Elements elementUtils;
        private Filer filer;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            elementUtils = processingEnvironment.getElementUtils();
            filer = processingEnvironment.getFiler();
        }
    
        // 指定SourceVersion
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }        
    }
    
  2. 指定我们所需要的annotation

    // 指定processortype
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        Set<Class<? extends Annotation>> supportedAnnotations = getSupportedAnnotations();
        for (Class<? extends Annotation> supportedAnnotation : supportedAnnotations) {
            types.add(supportedAnnotation.getCanonicalName());
        }
        return types;
    }
    
    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class);
        return annotations;
    }
    
  3. 在process中处理annotation

    首先将所有获取到的BindView细分到每个Activity中

    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    
    // 将获取到的bindview细分到每个class
    Map<Element, List<Element>> elementMap = new LinkedHashMap<>();
    
    for (Element element : elements) {
        // 返回activity
        Element enclosingElement = element.getEnclosingElement();
    
        List<Element> bindViewElements = elementMap.get(enclosingElement);
        if (bindViewElements == null) {
            bindViewElements = new ArrayList<>();
            elementMap.put(enclosingElement, bindViewElements);
        }
        bindViewElements.add(element);
    }
    

    通过遍历的方式处理每个细分的Activity

    // 生成代码
    for (Map.Entry<Element, List<Element>> entrySet : elementMap.entrySet()) {
        Element enclosingElement = entrySet.getKey();
        List<Element> bindViewElements = entrySet.getValue();
    
        // public final class xxxActivity_ViewBinding implements Unbinder
        // 获取activity的类名
        String activityClassNameStr = enclosingElement.getSimpleName().toString();
        System.out.println("------------->" + activityClassNameStr);
        ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
        ClassName unBinderClassName = ClassName.get("com.fastaoe.butterknife", "Unbinder");
        TypeSpec.Builder classBuilder =
                TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
                        .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
                        .addSuperinterface(unBinderClassName)
                        // 添加属性 private MainActivity target;
                        .addField(activityClassName, "target", Modifier.PRIVATE);
    
        // unbind()
        ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper");
        MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
                .addAnnotation(Override.class)
                .addAnnotation(callSuperClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
    
        // 构造函数
        MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
                .addParameter(activityClassName, "target")
                .addModifiers(Modifier.PUBLIC)
                // this.target = target
                .addStatement("this.target = target");
    
        for (Element bindViewElement : bindViewElements) {
            // textview
            String fieldName = bindViewElement.getSimpleName().toString();
            // Utils
            ClassName utilsClassName = ClassName.get("com.fastaoe.butterknife", "Utils");
            // R.id.textview
            int resourceId = bindViewElement.getAnnotation(BindView.class).value();
            // target.textview = Utils.findViewById(target, R.id.textview)
            constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)", fieldName, utilsClassName, resourceId);
            // target.textview = null
            unbindMethodBuilder.addStatement("target.$L = null", fieldName);
        }
    
    
        classBuilder.addMethod(unbindMethodBuilder.build())
                .addMethod(constructorMethodBuilder.build());
    
        // 获取包名
        String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
    
        try {
            JavaFile.builder(packageName, classBuilder.build())
                    .addFileComment("自己写的ButterKnife生成的代码,不要修改!!!")
                    .build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

在butterknife中编写注入代码

public class ButterKnife {

    public static Unbinder bind(Activity activity) {
        try {
            Class<? extends Unbinder> bindClazz = (Class<? extends Unbinder>)
                    Class.forName(activity.getClass().getName() + "_ViewBinding");
            // 构造函数
            Constructor<? extends Unbinder> bindConstructor = bindClazz.getDeclaredConstructor(activity.getClass());

            Unbinder unbinder = bindConstructor.newInstance(activity);
            return unbinder;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return Unbinder.EMPTY;
    }
}

使用

我们的使用方式就和正真的ButterKnife一样了:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.textview)
    TextView textview;

    private Unbinder bind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bind = ButterKnife.bind(this);

        textview.setText("修改后的文字");
    }

    @Override
    protected void onDestroy() {
        bind.unbind();
        super.onDestroy();
    }
}

最后

附上代码

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

推荐阅读更多精彩内容