Android ButterKnife框架理解-編譯時(shí)注解實(shí)踐

寫在前面

由于文章和代碼寫的比較久了
再次翻看閱讀,打開(kāi)工程運(yùn)行的時(shí)候,發(fā)現(xiàn)注解處理器不生效
浪費(fèi)了很多時(shí)間才搞清楚問(wèn)題所在,所以先記錄一下
之前使用的是

 classpath 'com.android.tools.build:gradle:3.0.0'

distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

由于AndroidStudio更新,再次clone代碼后,工程自動(dòng)更新配置
gradle 5.4.1版本,android tools3.5版本,發(fā)現(xiàn)注解處理器不生效

對(duì)比了ButterKnife 源碼的配置,發(fā)現(xiàn)還是使用的gradle4.10.3,android tools 3.4
索性降低版本試了下,發(fā)現(xiàn)是可以的

最終定位問(wèn)題原因:在Gradle 5.0將忽略compile classpath中的annotation processor,
需要手動(dòng)添加到annotation processor path

implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

ButterKnife優(yōu)勢(shì)

Android之神Jake Wharton寫的ButterKnife

ButterKnife是一個(gè)應(yīng)用于Android系統(tǒng)的View注入框架
可以減少大量的findViewById以及setOnClickListener代碼

優(yōu)勢(shì):

  1. 代碼清晰,可讀性強(qiáng)
  2. 簡(jiǎn)化Adapter中ViewHolder綁定
  3. 運(yùn)行時(shí)不會(huì)影響APP效率,使用配置方便
  4. View綁定和Click事件處理功能,簡(jiǎn)化代碼,提高開(kāi)發(fā)效率

具體用法就不做介紹了,參考官方使用即可

可能有人會(huì)對(duì)第三點(diǎn)有疑問(wèn),覺(jué)得使用了反射,影響了效率
其實(shí)看了ButterKnife的源碼就知道
反射只用在了創(chuàng)建XXXActivity_ViewBinding對(duì)象時(shí)
ButterKnife注解是編譯時(shí)注解并非運(yùn)行時(shí)注解

ButterKnife框架使用到的技術(shù)

自定義注解

參考鏈接:https://blog.csdn.net/kaifa1321/article/details/79622715

APT

APT(Annotation Process Tool),是一種在代碼編譯時(shí)處理注解,按照一定的規(guī)則,生成相應(yīng)的java文件
多用于對(duì)自定義注解的處理,目前比較流行的Dagger2, EventBus3,包括 ButterKnife,都是采用APT技術(shù)
對(duì)運(yùn)行時(shí)的性能影響很小

Androidstudio配置 APT

工程結(jié)構(gòu).png

android module : app, butterknife
java module : butterknife-compiler, butterknife-annotations
app 依賴butterknife
butterknife 依賴 java工程 butterknife-compiler
butterknife-annotations 里面就是放了自定義注解

工程的 build.gradle

buildscript {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        //新版本的Androidstudio 已經(jīng)不用這么配置了
        //classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

java-library butterknife-compiler的build.gradle

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc3'
    compile 'com.squareup:javapoet:1.9.0'
    compile project(path: ':butterknife-annotations')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

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

自定義注解 BindView 標(biāo)記屬性

/**
 * 編譯時(shí)注解  作用在屬性上   BindView
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}

MainActivity 使用注解 BindView

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv1)
    TextView mTextView1;
    @BindView(R.id.tv2)
    TextView mTextView2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mTextView1.setText("hello android");
        mTextView2.setText("hello Python");
    }
}

ButterKnife的bind方法

public class ButterKnife {
    public static UnBinder bind(Activity activity) {
        //獲取activity的Class
        Class<?> activityClass = activity.getClass();
        //獲取activity的全路徑名
        String activityName = activityClass.getName();
        //拼接className
        String className = activityName + "_ViewBinding";
        try {
            //獲取生成類的Class
            Class clazz = Class.forName(className);
            //獲取構(gòu)造函數(shù)
            Constructor constructor = clazz.getDeclaredConstructor(activityClass);
            //創(chuàng)建生成類的對(duì)象
            UnBinder unBinder = (UnBinder) constructor.newInstance(activity);
            return unBinder;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //如果出錯(cuò)返回EMPTY
        return UnBinder.EMPTY;
    }
}

activityName + "_ViewBinding這個(gè)類長(zhǎng)什么樣呢?
在app的build目錄下\build\generated\source\apt\debug\com\qingguoguo\followbutterknife

public final class MainActivity_ViewBinding implements UnBinder {
  private MainActivity target;

  public MainActivity_ViewBinding(MainActivity target) {
    this.target = target;
    this.target.mTextView1 = Utils.findViewById(target,2131165315);
    this.target.mTextView2 = Utils.findViewById(target,2131165316);
  }

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

MainActivity_ViewBinding 怎么生成的

封裝 findViewById方法

public class Utils {
    //封裝 findViewById方法
    public static <T extends View> T findViewById(Activity activity, int id) {
        return (T) activity.findViewById(id);
    }
}

生成代碼

//@AutoService(Processor.class)這個(gè)注解一定要
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

    private Filer mFile;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //初始化 java文件生成的位置
        mFile = processingEnvironment.getFiler();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    /**
     * 用來(lái)指定支持的 SourceVersion
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 用來(lái)指定支持的 AnnotationTypes
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    /**
     * 參考 ButterKnife 的寫法
     *
     * @return
     */
    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        //BindView目前只寫了這個(gè)注解
        annotations.add(BindView.class);
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 解析注解
        HashMap<Element, List<Element>> elementMap = new HashMap<>(16);
        //包含BindView注解的Set集合
        Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element : elementSet) {
            System.out.println("----" + element.getSimpleName() + "-----" + element.getEnclosingElement());
            /**
             *比較亂 并沒(méi)有給我們包裹好,需要自己組合,把一個(gè)類的所有屬性集合到一起
             *----mTextView1-----com.qingguoguo.followbutterknife.LoginActivity
             *----mTextView2-----com.qingguoguo.followbutterknife.LoginActivity
             *----mTextView1-----com.qingguoguo.followbutterknife.MainActivity
             *----mTextView2-----com.qingguoguo.followbutterknife.MainActivity
             */
            //拿到類名
            Element enclosingElement = element.getEnclosingElement();
            //Map中查找是否已經(jīng)存了該類
            List<Element> elements = elementMap.get(enclosingElement);
            if (elements == null) {
            //沒(méi)有找到,創(chuàng)建集合
                elements = new ArrayList<>();
                elementMap.put(enclosingElement, elements);
            }
            //把該屬性加到集合
            elements.add(element);
        }
        System.out.println("----" + elementMap + "-----");

        //生成代碼,遍歷Map集合
        Set<Map.Entry<Element, List<Element>>> entries = elementMap.entrySet();
        for (Map.Entry<Element, List<Element>> entry : entries) {
            Element element = entry.getKey();
            //父類
            ClassName unBinderClassName = ClassName.get("com.qingguoguo.butterknife", "UnBinder");
            //生成類
            //activity Class Name
            String activityClassNameStr = element.getSimpleName().toString();
            ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
            TypeSpec.Builder activityBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
                    .addModifiers(Modifier.FINAL, Modifier.PUBLIC);

            //---------添加父接口-----------------//
            activityBuilder.addSuperinterface(unBinderClassName);

            //---------添加屬性 private XXXActivity target ;-------//
            activityBuilder.addField(activityClassName, "target", Modifier.PRIVATE);

            //---------添加構(gòu)造方法  public xxx_ViewBinding(xxx target) -------------------//
            MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder();
            constructorMethodBuilder.addParameter(activityClassName, "target").addModifiers(Modifier.PUBLIC);
            constructorMethodBuilder.addStatement("this.target = target");

            //-------------------添加實(shí)現(xiàn)接口UnBinder 要重寫的unbind方法----------//
            ClassName callSuperClassName = ClassName.get("android.support.annotation",
                    "CallSuper");
            MethodSpec.Builder unbindMethodBuilder = MethodSpec
                    .methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addAnnotation(callSuperClassName).addModifiers(Modifier.PUBLIC);
            unbindMethodBuilder.addStatement("$T target = this.target", activityClassName);
            unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")");

            //-------------------給類中帶有注解的屬性賦值----------------------//
            List<Element> elementList = entry.getValue();
            for (Element viewBindIdElement : elementList) {
                String filedName = viewBindIdElement.getSimpleName().toString();
                int id = viewBindIdElement.getAnnotation(BindView.class).value();
                ClassName utilsClassName = ClassName.get("com.qingguoguo.butterknife",
                        "Utils");
                constructorMethodBuilder.addStatement("this.target.$L = $T.findViewById(target,$L)", filedName, utilsClassName, id);
                unbindMethodBuilder.addStatement("this.target.$L = null", filedName);
            }
            unbindMethodBuilder.addStatement("this.target = null");
            activityBuilder.addMethod(unbindMethodBuilder.build());
            activityBuilder.addMethod(constructorMethodBuilder.build());

            //-----------------包名----------------------//
            String packageName = mElementUtils.getPackageOf(element.getEnclosingElement()).getQualifiedName().toString();

            //----------------生成Java文件----------------//
            try {
                JavaFile.builder(packageName,
                        activityBuilder.build()).addFileComment("仿ButterKnife自動(dòng)生成").build().writeTo(mFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

斷點(diǎn)調(diào)試

剛開(kāi)始接觸APT不太熟悉的時(shí)候,肯定寫出來(lái)有很多問(wèn)題
如果你希望斷點(diǎn)調(diào)試,按照以往的經(jīng)驗(yàn)發(fā)現(xiàn)不起作用
因?yàn)榫幾g時(shí)在單獨(dú)的JVM里面執(zhí)行的,所以需要建立遠(yuǎn)程調(diào)試

  • 在項(xiàng)目的根目錄下gradle.properties 文件中加入如下兩條語(yǔ)句
org.gradle.jvmargs= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
org.gradle.parallel=true
  • run->Edit Config


    編輯配置
  • 創(chuàng)建遠(yuǎn)程Remote調(diào)試配置參數(shù)


    Remote調(diào)試.png
  • 點(diǎn)擊剛剛創(chuàng)建的配置,debug運(yùn)行


    debug運(yùn)行
  • rebuild


    rebuild

參考鏈接:https://blog.csdn.net/hongxue8888/article/details/99710884

其他鏈接

騰訊音樂(lè)技術(shù)團(tuán)隊(duì)-淺析ButterKnife

最后編輯于
?著作權(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)容