butterknife

首先从使用的地方来看:

@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.hello) Button hello;
@BindView(R.id.list_of_things) ListView listOfThings;
@BindView(R.id.footer) TextView footer;
@BindString(R.string.app_name) String butterKnife;
@BindString(R.string.field_method) String fieldMethod;
@BindString(R.string.by_jake_wharton) String byJakeWharton;
@BindString(R.string.say_hello) String sayHello;

上面是注解的,下面是在activity的oncreated方法使用的。注解的暂时先不管他,先看一看这个bind(this),方法做了什么!

  ButterKnife.bind(this);

 @NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}

可以看到这个sourceView其实就是activity的content布局的DecorView。
再跟踪到【createBinding】

findBindingConstructorForClass

image.png

这里的这个bindingCtor就是两个参数的构造方法。
然后是

 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

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

//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
  return constructor.newInstance(target, source);

这里就调用了两个参数的构造方法创建一个SimpleActivity_ViewBinding对象。

 @UiThread
public SimpleActivity_ViewBinding(final SimpleActivity 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);
view2131099658 = 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);
view2131099666 = 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'"));

Context context = source.getContext();
Resources res = context.getResources();
target.butterKnife = res.getString(R.string.app_name);
target.fieldMethod = res.getString(R.string.field_method);
target.byJakeWharton = res.getString(R.string.by_jake_wharton);
target.sayHello = res.getString(R.string.say_hello);

}
这样就完成了绑定。
这个SimpleActivity_ViewBinding类是通过butterknife-compiler自动生成的。
这些代码是怎么生成的呢?
首先我们能用的注解是定义在

image.png

对这些注解的处理主要是在compiler包下面的ButterKnifeProcessor。的process方法。

 @Override
 public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
  Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

  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;

}

这里面JavaFile是javapoet这个开源库里面的工具类,主要为了生成类,待会再看
在生成之前findAndParseTargets(env)查找和解析注解。

image.png

其中的一段代码片段。其他的注解的处理类似都是走parseXXXXX。重点看下最常用的BindView吧。

 // Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();

拿到注解传入的id参数。

builder = getOrCreateBindingBuilder(builderMap, enclosingElement);

处理完解析之后,是时候来看一看编译器自动生成java类的代码了。

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

brewJava方法

  JavaFile brewJava(int sdk, boolean debuggable) {
return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
    .addFileComment("Generated code from Butter Knife. Do not modify!")
    .build();
  }

createType方法

 private TypeSpec createType(int sdk, boolean debuggable) {
TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
    .addModifiers(PUBLIC);
if (isFinal) {
  result.addModifiers(FINAL);
}

if (parentBinding != null) {
  result.superclass(parentBinding.bindingClassName);
} else {
  result.addSuperinterface(UNBINDER);
}

if (hasTargetField()) {
  result.addField(targetTypeName, "target", PRIVATE);
}

if (isView) {
  result.addMethod(createBindingConstructorForView());
} else if (isActivity) {
  result.addMethod(createBindingConstructorForActivity());
} else if (isDialog) {
  result.addMethod(createBindingConstructorForDialog());
}
if (!constructorNeedsView()) {
  // Add a delegating constructor with a target type + view signature for reflective use.
  result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk, debuggable));

if (hasViewBindings() || parentBinding == null) {
  result.addMethod(createBindingUnbindMethod(result));
}

return result.build();
}

createBindingConstructor方法

  private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
    .addAnnotation(UI_THREAD)
    .addModifiers(PUBLIC);

if (hasMethodBindings()) {
  constructor.addParameter(targetTypeName, "target", FINAL);
} else {
  constructor.addParameter(targetTypeName, "target");
}

if (constructorNeedsView()) {
  constructor.addParameter(VIEW, "source");
} else {
  constructor.addParameter(CONTEXT, "context");
}

if (hasUnqualifiedResourceBindings()) {
  // Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
  constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
      .addMember("value", "$S", "ResourceType")
      .build());
}

if (hasOnTouchMethodBindings()) {
  constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
      .addMember("value", "$S", "ClickableViewAccessibility")
      .build());
}

if (parentBinding != null) {
  if (parentBinding.constructorNeedsView()) {
    constructor.addStatement("super(target, source)");
  } else if (constructorNeedsView()) {
    constructor.addStatement("super(target, source.getContext())");
  } else {
    constructor.addStatement("super(target, context)");
  }
  constructor.addCode("\n");
}
if (hasTargetField()) {
  constructor.addStatement("this.target = target");
  constructor.addCode("\n");
}

if (hasViewBindings()) {
  if (hasViewLocal()) {
    // Local variable in which all views will be temporarily stored.
    constructor.addStatement("$T view", VIEW);
  }
  for (ViewBinding binding : viewBindings) {
    addViewBinding(constructor, binding, debuggable);
  }
  for (FieldCollectionViewBinding binding : collectionBindings) {
    constructor.addStatement("$L", binding.render(debuggable));
  }

  if (!resourceBindings.isEmpty()) {
    constructor.addCode("\n");
  }
}

if (!resourceBindings.isEmpty()) {
  if (constructorNeedsView()) {
    constructor.addStatement("$T context = source.getContext()", CONTEXT);
  }
  if (hasResourceBindingsNeedingResource(sdk)) {
    constructor.addStatement("$T res = context.getResources()", RESOURCES);
  }
  for (ResourceBinding binding : resourceBindings) {
    constructor.addStatement("$L", binding.render(sdk));
  }
}

return constructor.build();
  }

binding.render(sdk)对view绑定的关键在这里

 CodeBlock render(boolean debuggable) {
CodeBlock.Builder builder = CodeBlock.builder()
    .add("target.$L = $T.$L(", name, UTILS, kind.factoryName);
for (int i = 0; i < ids.size(); i++) {
  if (i > 0) {
    builder.add(", ");
  }
  builder.add("\n");

  Id id = ids.get(i);
  boolean requiresCast = requiresCast(type);
  if (!debuggable) {
    if (requiresCast) {
      builder.add("($T) ", type);
    }
    builder.add("source.findViewById($L)", id.code);
  } else if (!requiresCast && !required) {
    builder.add("source.findViewById($L)", id.code);
  } else {
    builder.add("$T.find", UTILS);
    builder.add(required ? "RequiredView" : "OptionalView");
    if (requiresCast) {
      builder.add("AsType");
    }
    builder.add("(source, $L, \"field '$L'\"", id.code, name);
    if (requiresCast) {
      TypeName rawType = type;
      if (rawType instanceof ParameterizedTypeName) {
        rawType = ((ParameterizedTypeName) rawType).rawType;
      }
      builder.add(", $T.class", rawType);
    }
    builder.add(")");
  }
}
return builder.add(")").build();
}

间接调用findviewbyid()

 public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
  return view;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容