【Android】注解框架(三)-- 編譯時(shí)注解,手寫B(tài)utterKnife

目錄

  1. 【Android】注解框架(一)-- 基礎(chǔ)知識(shí)Java 反射
  2. 【Android】注解框架(二)-- 基礎(chǔ)知識(shí)(Java注解)& 運(yùn)行時(shí)注解框架
  3. 【Android】注解框架(三)-- 編譯時(shí)注解,手寫B(tài)utterKnife
  4. 【Android】注解框架(四)-- 一行代碼注入微信支付

apt介紹

作為Android程序員應(yīng)該絕大部分分人都用過(guò)ButterKnife,Retrofit等框架,這些框架只需要在用的時(shí)候使用注解,就可以直接使用了,非常方便。并且這些框架并沒(méi)有減少性能。

那么這些框架做了哪些東西呢?

我們以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();
}

上面是我們平常使用的時(shí)候所使用的代碼,這些代碼到底做了什么呢?

  1. 我們給元素寫上注解
  2. 在編譯器執(zhí)行的環(huán)節(jié),系統(tǒng)收集我們所寫注解的元素,并將這些元素組合成Java代碼MainActivity_ViewBinding
  3. 當(dāng)我們調(diào)用ButterKnife.bind的時(shí)候,通過(guò)動(dòng)態(tài)注入的方式,將MainActivityMainActivity_ViewBinding關(guān)聯(lián)起來(lái),并給所有的注解所在的元素賦值。

而這些東西的核心就是在編譯期間生成我們所需要的Java文件,這樣我們?cè)谑褂玫臅r(shí)候就基本沒(méi)有性能的影響。

網(wǎng)上找到的APT的流程圖:

手寫B(tài)utterKnife

通過(guò)上面的圖我們先生成這些module

butterknife_流程圖

app - 主項(xiàng)目
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這兩個(gè)lib是用來(lái)方便我們生成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. 創(chuàng)建類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細(xì)分到每個(gè)Activity中

    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    
    // 將獲取到的bindview細(xì)分到每個(gè)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);
    }
    

    通過(guò)遍歷的方式處理每個(gè)細(xì)分的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);
    
        // 構(gòu)造函數(shù)
        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");
            // 構(gòu)造函數(shù)
            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();
    }
}

最后

附上代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容