讀源碼-ButterKnife源碼解析


本文基於ButterKnife版本:

'com.jakewharton:butterknife:10.2.1'

'com.jakewharton:butterknife-compiler:10.2.1'

1-自定義註解處理器

java代碼編譯期,javac會調用java註解器來處理註解相關。先看下butterknife-compiler庫中的核心ButterKnifeProcessor

通過繼承AbstractProcessor實現自定義處理器,重寫以下方法

public class ButterKnifeProcessor extends AbstractProcessor {
    
    //指定java版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    
    //(1)初始化輔助類
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        String sdk = env.getOptions().get(OPTION_SDK_INT);
        if (sdk != null) {
        try {
            this.sdk = Integer.parseInt(sdk);
        } catch (NumberFormatException e) {
            env.getMessager()
                .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                    + sdk
                    + "'. Falling back to API 1 support.");
        }
        debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        try {
            trees = Trees.instance(processingEnv);
        } catch (IllegalArgumentException ignored) {
            try {
                // Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
                for (Field field : processingEnv.getClass().getDeclaredFields()) {
                  if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
                    field.setAccessible(true);
                    ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
                    trees = Trees.instance(javacEnv);
                    break;
                  }
                }
            } catch (Throwable ignored2) {
            }
        }
    }
    
    //返回目標註解集合
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }
    
    //(2)核心方法。先掃描查找註解,再生成java文件
    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //@1.查找註解信息,生成BindingSet並保存到bingdingMap中
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //@4.遍歷bindingMap,生成對應的 className_ViewBinding.java文件
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }
}

(1)初始相關輔助類init()

init方法中涉及的幾個輔助類\變量

(2)核心操作process()方法
@1.掃描查找註解

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
    // @BindView 註解
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        //@2.處理BindView的註解
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    //省略@BindViews @BindString等註解處理
    。。。
    
    //@3.監聽處理,OnClick,OnItemClick等
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }
    
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
      if (parentType != null) {
        BindingClass bindingClass = entry.getValue();
        BindingClass parentBindingClass = targetClassMap.get(parentType);
        bindingClass.setParent(parentBindingClass);
      }
    }
}

@2.處理@BindView。這裏涉及到BindingSet輔助類,緩存相關注解信息。ViewBiding

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // 檢測註解語法是否合法。註解方法不能爲private、static,註解類不能是android/java包
    // 若非法,拋出異常
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }
    //不合法,返回
    if (hasError) {
      return;
    }

    // 獲取註解中的對應的viewId
    int id = element.getAnnotation(BindView.class).value();
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    if (builder != null) {
        //判斷ID是否已經綁定過,如果已綁定則拋出異常
      String existingBindingName = builder.findExistingBindingName(resourceId);
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
        //未綁定過,則創建BindingSet.Builder
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
    //將當前註解生成FieldViewBinding並添加到builder中
    builder.addField(resourceId, new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

@3.監聽處理,OnClick,OnItemClick等

private void findAndParseListener(RoundEnvironment env,
      Class<? extends Annotation> annotationClass,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
    for (Element element : env.getElementsAnnotatedWith(annotationClass)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        //核心方法,解析註解
        //並將listener註解對應的信息封裝到MethodViewBinding中
        //將MethodViewBinding添加到builder中
        parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        StringWriter stackTrace = new StringWriter();
        e.printStackTrace(new PrintWriter(stackTrace));

        error(element, "Unable to generate view binder for @%s.\n\n%s",
            annotationClass.getSimpleName(), stackTrace.toString());
      }
    }
  }

(3)註冊自定義註解器。通過google提供的auto-service庫來通過註解註冊

compile 'com.google.auto.service:auto-service:1.0-rc2

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    ...
}

2-生成對應的className_ViewBinding.java

@4.遍歷bindingMap,生成對應的 className_ViewBinding.java文件。例如業務使用

//ActivityB.java
public class ActivityB extends BaseActivity{
    @BindView(R.id.edit_txt)
    EditText editText;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        //@1.綁定
        ButterKnife.bind(this);
    }

    @OnClick(R.id.btn_one)
    protected void postMsg() {
        String msg = editText.getText().toString();
        EventBus.getDefault().post(MessageWrap.getInstance(msg));
        this.finish();
    }
}

經過安裝編譯後在指定目錄下生成了ActivityB_ViewBinding.java。對應的方法都是指定在UI線程執行,且通過持有目標引用直接調用對應的屬性或方法,所以要求註解的變量或方法必須爲非private、static

public class ActivityB_ViewBinding implements Unbinder {
  private ActivityB target;//目標類的引用

  private View view7f070044;//與click事件綁定的view

  @UiThread
  public ActivityB_ViewBinding(ActivityB target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public ActivityB_ViewBinding(final ActivityB target, View source) {
    this.target = target;
    //實現findViewById及setOnClickListener
    View view;
    target.editText = Utils.findRequiredViewAsType(source, R.id.edit_txt, "field 'editText'", EditText.class);
    view = Utils.findRequiredView(source, R.id.btn_one, "method 'postMsg'");
    view7f070044 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.postMsg();
      }
    });
  }
    //解綁
  @Override
  @CallSuper
  public void unbind() {
    ActivityB target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.editText = null;

    view7f070044.setOnClickListener(null);
    view7f070044 = null;
  }
}

使用原理,在ActivityB.setContentView之後調用ButterKnife.bind(this)來完成綁定。–>ButterKnife.bind(target, sourceView)。target即爲ActivityB的實例,sourceView爲其DecorView

@NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    //通過ActivityB的類名拼接獲取到對應的ActivityB_ViewBinding.java類名
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //@5.獲取對應ViewBinding類的構造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
        //創建對應的ViewBinding類實例
        //即在ViewBinding初始化時完成了ActivityB的
        //findViewById及setOnClickListener等操作
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

@5.獲取對應ViewBinding類的構造器

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    //從緩存中讀取對應class的構造器
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    //java\android\androidx包下的類不處理
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
        //獲取ClassLoader並加載對應的ViewBinding類
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //通過反射獲取ViewBinding的Class對象的Constructor即構造器
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    //緩存構造器
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

3-流程總結

  • (1)自定義AnnotationProcessor,ButterKnifeProcessor並註冊
  • (2).java–>.class的過程中,通過ButterKnifeProcessor生成ViewBinding類文件
    • 處理@BindView@BindViews等註解,根據註解類型封裝相關信息到ViewBinding、ResourceBinding等
    • 處理@OnClick@OnItemClick等Listener註解,將相關注解信息及對應的方法信息封裝到MethodViewBinding
    • 這些註解信息都在class對應的註解輔助類BindingSet中,以綁定類的TypeElement爲key,類中所有註解信息BindingSet爲value,添加到bindingMap中
    • 遍歷bindingMap,通過BindingSet相關信息生成綁定類的對應的ViewBinding類文件。
  • (3)Activity中調用ButterKnife.bind(this)
  • (4)反射獲取編譯期生成的Activity_ViewBinding文件,通過ClassLoader加載,反射獲取其構造方法並調用
  • (5)ViewBinding類的構造方法中持有目標類的引用,調用目標類的findViewbyId、setOnClickListener等操作完成綁定。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章