Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 深入理解ButterKnife源碼並掌握原理(三)

深入理解ButterKnife源碼並掌握原理(三)

編輯:關於Android編程

上兩篇我們分析完了處理器的process方法的findAndParseTargets方法來獲取了一個集合,該集合包含了你使用注解的類的TypeElement和這個類中的注解的實例BindingClass。
我們再看下處理器的核心方法 process方法

 @Override public boolean process(Set elements, RoundEnvironment env) {
 //這個方法查找所有的注解信息,並形成BindingClass集合
    Map targetClassMap = findAndParseTargets(env);

//遍歷生存生成java 文件
    for (Map.Entry entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

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

//表示處理器正確處理了
    return true;
  }

我們先看一下BindingClass的一些成員變量。這裡我們主要是看關鍵的一些集合

  //這個是BindView 注解在findAndParseTargets 方法解析後 形成ViewBindings實體通過該類的addxx方法添加到viewIdMap集合的對象

  private final Map viewIdMap = new LinkedHashMap<>();

     //這個是BindViews 注解 在findAndParseTargets 方法解析後 形成的.
     BindViews用法:

     @BindViews({ R2.id.title, R2.id.subtitle, R2.id.hello }) List headerViews;


     //key:FieldCollectionViewBinding 為實體,包含了名字,類型等信息,大家可以看下源碼,這裡就不說了就是一個java-bean ,你可以這麼理解。
     //value:
     就是這些id: R2.id.title, R2.id.subtitle, R2.id.hello 


  private final Map> collectionBindings =
      new LinkedHashMap<>();

      //該集合是BindBitmap注解在findAndParseTargets 注解在findAndParseTargets 方法解析後 形成FieldBitmapBinding實體通過該類的addxx方法添加到bitmapBindings集合的對象

  private final List bitmapBindings = new ArrayList<>();
  //這個就是BindDrawable注解....
  private final List drawableBindings = new ArrayList<>();
  //這個就是BindFloat,BindInt,BindString... 這些注解....
  private final List resourceBindings = new ArrayList<>();

上面的addxxx方法類似下面的,當然還有其它的就不貼出了

void addFieldCollection(List ids, FieldCollectionViewBinding binding) {
    collectionBindings.put(binding, ids);
  }

  boolean addMethod(
      Id id,
      ListenerClass listener,
      ListenerMethod method,
      MethodViewBinding binding) {
    ViewBindings viewBindings = getOrCreateViewBindings(id);
    if (viewBindings.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) {
      return false;
    }
    viewBindings.addMethodBinding(listener, method, binding);
    return true;
  }

  void addResource(FieldResourceBinding binding) {
    resourceBindings.add(binding);
  }  

  //......

至此我們算是完成了一個大步走,我們把所有的注解 方法等放倒了一個集合裡,生成java文件的時候就可以遍歷這些集合了,從而形成有一定規律的java源文件。
形成的文件就是:
這裡我們拿官方的demo-SimpleActivity
編譯完後最後生成的文件為:SimpleActivity_ViewBinding
離目標越來越近了。哈哈

.java文件生成

生成.java文件你可以用字符串拼接的笨方法,但是我們的jake大神怎麼可能那麼弱。人家自己又寫了一個庫。javapoet ,66666
666

    compile 'com.squareup:javapoet:1.7.0'

這個庫可以很方便的生成我們想要的java文件。
在findAndParseTargets方法

 //...
 //遍歷生存生成java 文件
    for (Map.Entry entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

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

遍歷targetClassMap集合,調用每一個類的brewJava()方法,最後返回JavaFile對象,再通過writeTo方法生成java文件。
接下來我們就看下bindingClass.brewJava()方法。

//我們可以看到javapoet這個開源庫使用了設計模式-builder模式。
// 我們看源碼就是看這些模式啦,思想啦,才能提高自己的代碼編寫能力
//bug少了才有時間干別的呀,哈哈 。。。你懂我的

  JavaFile brewJava() {
  //參數1:包名
   //參數2:TypeSpec,這個可以生成class ,interface 等java文件
    return JavaFile.builder(bindingClassName.packageName(), createBindingClass())
    //注釋
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();  }

主要步驟
1. 生成類名
2. 生成構造函數
3. 生成unbind方法。

  private TypeSpec createBindingClass() {

  1. 生成類名
  //bindingClassName.simpleName() 就是我們在findAndParseTargets方法形成的類的名字:xxx_ViewBinding名字
  xxx也就是使用butterknife的類名

    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
    //增加類修飾符public
        .addModifiers(PUBLIC);

 //使用butterknife的類是 final類
    TypeName targetType;
    if (isFinal) {
     //增加類修飾符FINAL
      result.addModifiers(FINAL);
      targetType = targetTypeName;
    } else {
    //不是final類,增加泛型參數T
      targetType = TypeVariableName.get("T");
      result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
    }

//如果有父類直接繼承
    if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType));
    } else {
    //增加實現接口
      result.addSuperinterface(UNBINDER);
       //增加成員變量
      result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED);
    }

    //到這裡生成的代碼如下:
    //比如在activity
    public class SimpleActivity_ViewBinding implements Unbinder {
  protected T target;
  }
  //比如在adapter的viewholder中
public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder {
  private SimpleAdapter.ViewHolder target;
}


2. 生成構造函數

    if (!bindNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor(targetType));
    }
    result.addMethod(createBindingConstructor(targetType));

  3. 生成unbind方法。
    if (hasViewBindings() || !hasParentBinding()) {
      result.addMethod(createBindingUnbindMethod(result, targetType));
    }

    return result.build();
  }

(一)生成類名

代碼裡注釋的比較詳細了

(二)生成構造函數

主要是createBindingConstructor 方法,主要是對成員變量賦值,以及設置監聽事件。先看下javapoet提供的幾個方法或類:

1.MethodSpec:生成方法的輔助類

這裡主要是生成構造函數,當然也可以生成其它的普通方法,構造函數也是方法的一種嗎。
通過constructorBuilder構造出一個方法,通過addAnnotation添加注解,通過addModifiers添加修飾符。

  MethodSpec.Builder constructor = 
  MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);

通過如下方法添加參數targetType為參數類型,”target”為參數的變量名

     constructor.addParameter(targetType, "target");

通過如下方法添加代碼語句
第一參數是String類型,可以有占位符S(字符串類型),T(類型)等
第二個參數Object… args,類型,多參數不固定。
就像你平時使用String.format()方法一樣的意思.

   constructor.addStatement("this.target = target");
2.構造函數中變量的賦值

這裡主要是對使用了如下的代碼進行一個賦值操作。

  @BindView(R2.id.title) TextView title;
 @OnClick(R2.id.hello) void sayHello() {}

這裡我們主要看一下關鍵方法,因為都是類似的拼接代碼字符串。
我們看一下變量的賦值:

  private void addViewBindings(MethodSpec.Builder result, ViewBindings bindings) {
    if (bindings.isSingleFieldBinding()) {
      // Optimize the common case where there's a single binding directly to a field.
      FieldViewBinding fieldBinding = bindings.getFieldBinding();
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());

      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!requiresCast && !fieldBinding.isRequired()) {
        builder.add("source.findViewById($L)", bindings.getId().code);
      } else {
        builder.add("$T.find", UTILS);
        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          builder.add("AsType");
        }
        builder.add("(source, $L", bindings.getId().code);
        if (fieldBinding.isRequired() || requiresCast) {
          builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
        }
        if (requiresCast) {
          builder.add(", $T.class", fieldBinding.getRawType());
        }
        builder.add(")");
      }
      result.addStatement("$L", builder.build());
      return;
    }

    List requiredViewBindings = bindings.getRequiredBindings();
    if (requiredViewBindings.isEmpty()) {
      result.addStatement("view = source.findViewById($L)", bindings.getId().code);
    } else if (!bindings.isBoundToRoot()) {
      result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS,
          bindings.getId().code, asHumanDescription(requiredViewBindings));
    }

    addFieldBindings(result, bindings);
    addMethodBindings(result, bindings);
  }

主要是調用系統的findViewById 方法,但是你看到了findRequiredViewAsType,findRequiredView方法和castView方法,findRequiredView,findRequiredViewAsType是作者為樂代碼的書寫方便對findViewById的一層封裝,你可以看一下源碼,最後都會調用的findRequiredView方法的findViewById方法。

 public static View findRequiredView(View source, @IdRes int id, String who) {
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
 }

這個castView是什麼方法呢?是Class類的方法,直接轉換為指定的類型

   public static  T castView(View view, @IdRes int id, String who, Class cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }

說白了都是調用系統的方法。
好了到這裡成員變量的賦值算是完了。
注意一點target.title target就是我們的activity或者view ;也驗證了為什麼是使用了類似BindView注解不能是private修飾符的另一個原因了。

接下來是方法的監聽 private void addMethodBindings(MethodSpec.Builder result, ViewBindings bindings) {}方法,李 main 也是通過循環添加方法借助我們上文提到的
MethodSpec.methodBuilder構造器

  for (ListenerMethod method : getListenerMethods(listener)) {
        MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name())
            .addAnnotation(Override.class)
            .addModifiers(PUBLIC)
            .returns(bestGuess(method.returnType()));
        String[] parameterTypes = method.parameters();
        for (int i = 0, count = parameterTypes.length; i < count; i++) {
          callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);
          //...
        }

感興趣的可以根據生成的代碼來對照這查看,這裡就不多說了。
最後生成的如下所示的代碼。

  @UiThread
  public SimpleActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
    view2130968579 = view;
    ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

(三)生成unbind方法

createBindingUnbindMethod方法主要是把的成員變量啦,Listener等 置為空,比如setOnClickListener(null)。

    private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass,
      TypeName targetType) {
    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC);
    if (!isFinal && !hasParentBinding()) {
      result.addAnnotation(CALL_SUPER);
    }
    boolean rootBindingWithFields = !hasParentBinding() && hasFieldBindings();
    if (hasFieldBindings() || rootBindingWithFields) {
      result.addStatement("$T target = this.target", targetType);
    }
    if (!hasParentBinding()) {
      String target = rootBindingWithFields ? "target" : "this.target";
      result.addStatement("if ($N == null) throw new $T($S)", target, IllegalStateException.class,
          "Bindings already cleared.");
    } else {
      result.addStatement("super.unbind()");
    }

    if (hasFieldBindings()) {
      result.addCode("\n");
      for (ViewBindings bindings : viewIdMap.values()) {
        if (bindings.getFieldBinding() != null) {
          result.addStatement("target.$L = null", bindings.getFieldBinding().getName());
        }
      }
      for (FieldCollectionViewBinding fieldCollectionBinding : collectionBindings.keySet()) {
        result.addStatement("target.$L = null", fieldCollectionBinding.getName());
      }
    }

    if (hasMethodBindings()) {
      result.addCode("\n");
      for (ViewBindings bindings : viewIdMap.values()) {
        addFieldAndUnbindStatement(bindingClass, result, bindings);
      }
    }

    if (!hasParentBinding()) {
      result.addCode("\n");
      result.addStatement("this.target = null");
    }

    return result.build();
  }

主要就是addStatement方法,上文已經說了,該方法的意思就是生成一句代碼,
第一參數是String類型,可以有占位符S(字符串類型),T(類型)等
第二個參數Object… args,類型,多參數不固定。
就像你平時使用String.format()方法一樣的意思.
比較簡單,最後生成的方法類似:

  @Override
  public void unbind() {
    SimpleAdapter.ViewHolder target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.word = null;
    target.length = null;
    target.position = null;

    this.target = null;
  }
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved