ButterKnife 原理及部分實現代碼

1.ButterKnife介紹

主要是解決掉 findViewById 和 setOnclick ,還包括資源的注入 ,編譯時註解。java

2. ButterKnife原理分析

主要採用編譯時註解,就是用 apt 生成代碼android

3.註解處理器是什麼?

註解處理器是(Annotation Processor)是javac的一個工具,用來在編譯 時掃描註解(Annotation)。bash

3.理解處理器AbstractProcessor

public class ButterKnifeProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv){
        super.init(processingEnv);
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return null;
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();//使用最新支持版本
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return true;
    }
}
複製代碼
  • init():會自動被註解處理工具調用,經過並傳入ProcessingEnviroment參數參數能夠獲取到不少有用的工具類:Elements , Types , Filer等工具
  • getSupportedAnnotationTypes()指定註解處理器是註冊給哪個註解的(Annotation),它是一個字符串的集合,能夠支持多個類型的註解(Annotation),註解(Annotation)指定必須是完整的路徑。
  • process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 這個也是最主要的,掃描出的結果會存儲進roundEnv中,能夠在這裏獲取到註解內容,編寫你的操做邏輯。注意,process()函數中不能直接進行異常拋出,不然的話,運行Annotation Processor的進程會異常崩潰,而後彈出一大堆讓人捉摸不清的堆棧調用日誌顯示。
  • getSupportedSourceVersion():用於指定你的java版本,通常使用:SourceVersion.latestSupported()

3. 本身動手實現

工程介紹

如下配置以 [Android Studio 3.2.1] 爲例

代碼 ButterKnife ViewBind 爲例子app

項目工程分爲4部分

app主工程
ButterKnife分爲3個module
      butterknife-annotation(定義註解)
      butterknife-complier(AbstractProcessor 處理器代碼)
      butterknife(ButterKnife.bind 代碼)ide

Gradle配置

app.gradle
在gradle配置文件中添加‘annotationProcessorOptions.includeCompileClasspath = true’的配置,若是不添加該配置,編譯時將會報錯。
使用implementation引入工程 butterknife_annotations、butterknife
使用annotationProcessor引入butterknife_compiler
函數

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.maqf.androidplugindemo"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    implementation project(':butterknife_annotations')
    implementation project(':butterknife')
    annotationProcessor project(':butterknife_compiler')
}
複製代碼

butterknife-complier gradle
引入AbstractProcessor依賴庫工具

compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
複製代碼

完整的gradle以下gradle

apply plugin: 'java-library'

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')
}

sourceCompatibility = "7"
targetCompatibility = "7"
複製代碼

MainActivty.javaui

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.btn)
    Button mBtn ;


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

    @CheckNet
    public void click(View view){
        startActivity(new Intent(this,ProxyActivity.class));
    }
}

複製代碼

經過註解生成的類 MainActivity_ViewBindingthis

public final class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this.target = target;
    target.mBtn = Utils.findViewById(target,2131165218);
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");;
    this.target = null;;
    target.mBtn = null;
  }
}
複製代碼

butterknife工程的關鍵類ButterKnife
經過反射建立 MainActivity_ViewBinding 對象

public class ButterKnife {
   public static Unbinder bind(Activity activity){
       try {
           
           Class<?> clazz = Class.forName(activity.getClass().getName()+"_ViewBinding" ) ;
           Constructor<?> cons = clazz.getConstructor(activity.getClass());
           Unbinder unbinder = (Unbinder) cons.newInstance(activity);
           return unbinder ;
       } catch (Exception e) {
           e.printStackTrace();
       }

       return Unbinder.EMPTY ;
   }
}

複製代碼

ButterKnife-annotation工程
RetentionPolicy.CLASS 做用域class字節碼上,生命週期只有在編譯器間有效。編譯時註解註解處理器的實現主要依賴於AbstractProcessor來實現

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

ButterKnife-complier工程

@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() ;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        System.out.println("------------------------->");
        //BindView註解的Element
        Set<? extends Element>  bindViewElements  = roundEnvironment.getElementsAnnotatedWith(BindView.class) ;
        Map<Element,List<Element>> elementListMap = new LinkedHashMap<>() ;
        for(Element element:bindViewElements ){
            //Activity 類的Element
            Element enclosingElement = element.getEnclosingElement() ;
            List<Element> listElement = elementListMap.get(enclosingElement) ;
            if(listElement==null){
                listElement = new ArrayList<>() ;
                elementListMap.put(enclosingElement,listElement) ;
            }
            listElement.add(element) ;
        }
        //遍歷map 生成類
        for(Map.Entry<Element, List<Element>> entry :elementListMap.entrySet()){
            Element enclosingElement = entry.getKey();
            List<Element> elements = entry.getValue();
            //生成類 classNameStr_ViewBinding
            String classNameStr = enclosingElement.getSimpleName().toString() ;
            ClassName activityClassName = ClassName.bestGuess(classNameStr) ;
            ClassName superClass = ClassName.get("com.maqf.butterknife","Unbinder") ;
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(classNameStr+"_ViewBinding")
                    .addModifiers(Modifier.FINAL,Modifier.PUBLIC) //類的屬性,public final 
                    .addSuperinterface(superClass) //類實現的接口
                    .addField(activityClassName,"target",Modifier.PRIVATE); //設置屬性 

            //unbind方法 實際代碼樣式
            /**
             * @Override
             *   @CallSuper
             *   public void unbind() {
             *     MainActivity target = this.target;
             *     if (target == null) throw new IllegalStateException("Bindings already cleared.");
             *     this.target = null;
             *
             *     target.textView1 = null;
             *     target.textView2 = null;
             *   }
             */
            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.\");") ;
            unbindMethodBuilder.addStatement("this.target = null;") ;


            //構造函數
            ClassName uiThreadClassName = ClassName.get("android.support.annotation", "UiThread");
            MethodSpec.Builder  constructorBuilder = MethodSpec.constructorBuilder()
                    .addAnnotation(uiThreadClassName)
                    .addParameter(activityClassName,"target")
                    .addModifiers(Modifier.PUBLIC) ;
            //   this.target = target;
            constructorBuilder.addStatement("this.target = target") ;

            for(Element element:elements){
                // target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
                //   target.textView2 = Utils.findRequiredViewAsType(source, R.id.tv2, "field 'textView2'", TextView.class);
                int viewId = element.getAnnotation(BindView.class).value();
                String fieldName = element.getSimpleName().toString() ;
                ClassName utilClassName = ClassName.get("com.maqf.butterknife","Utils") ;
                constructorBuilder.addStatement("target.$L = $T.findViewById(target,$L)",fieldName,utilClassName,viewId) ;

                /**
                 * target.textView1 = null;
                 * target.textView2 = null;
                 */
                unbindMethodBuilder.addStatement("target.$L = null",fieldName) ;

            }

            classBuilder.addMethod(constructorBuilder.build()) ;
            classBuilder.addMethod(unbindMethodBuilder.build());
            /**
             * 獲取包名
             */
            String packageName = mElementUtils.getPackageOf(enclosingElement).toString() ;
            try {
                JavaFile.builder(packageName,classBuilder.build())
                        .addFileComment("butterknife 自動生成")
                        .build()
                        .writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }



        return false;
    }

    /**
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    /**
     * @return
     */
    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class);
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
複製代碼
相關文章
相關標籤/搜索