前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开源框架源码解析系列(3)——ButterKnife源码解析

Android开源框架源码解析系列(3)——ButterKnife源码解析

作者头像
老马的编程之旅
发布2022-06-22 13:21:32
9210
发布2022-06-22 13:21:32
举报
文章被收录于专栏:深入理解Android

ButterKnife是一个专注于Android系统的View注入框架,有了ButterKnife可以很轻松的省去findViewById,ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class,对运行时性能没有影响,本篇我们来详细学习一下它的源码。

1.ButterKnife的使用

ButterKnife项目地址:https://github.com/JakeWharton/butterknife

1.1 如何接入

  1. 在Project的 build.gradle 中添加如下代码:
代码语言:javascript
复制
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
    
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'  //添加这一行
    }
}
  1. 在App的 build.gradle 中添加如下代码:
代码语言:javascript
复制
apply plugin: 'com.jakewharton.butterknife'
  1. dependencies中添加:
代码语言:javascript
复制
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

1.2 ButterKnife使用心得与注意事项

1、在Activity 类中绑定 :ButterKnife.bind(this);必须在setContentView();之后绑定;且父类bind绑定后,子类不需要再bind。 2、在非Activity 类(eg:Fragment、ViewHold)中绑定: ButterKnife.bind(this,view);这里的this不能替换成getActivity()。 3、在Activity中不需要做解绑操作,在Fragment 中必须在onDestroyView()中做解绑操作。 4、使用ButterKnife修饰的方法和控件,不能用private or static 修饰,否则会报错。错误: @BindView fields must not be private or static. 5、setContentView()不能通过注解实现。(其他的有些注解框架可以) 6、使用Activity为根视图绑定任意对象时,如果你使用类似MVC的设计模式你可以在Activity 调用ButterKnife.bind(this, activity),来绑定Controller。 7、使用ButterKnife.bind(this,view)绑定一个view的子节点字段。如果你在子View的布局里或者自定义view的构造方法里 使用了inflate,你可以立刻调用此方法。或者,从XML inflate来的自定义view类型可以在onFinishInflate回调方法中使用它。

1.3 ButterKnife基本使用

  1. 在Activity中绑定ButterKnife: 建议写一个BaseActivity完成绑定,子类继承即可。绑定Activity 必须在setContentView之后。使用ButterKnife.bind(this)进行绑定
代码语言:javascript
复制
public class MainActivity extends AppCompatActivity{  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        //绑定初始化ButterKnife  
        ButterKnife.bind(this);  
    }  
}  
  1. 在Fragment中绑定ButterKnife
代码语言:javascript
复制
public class ButterknifeFragment extends Fragment{  
    private Unbinder unbinder;  
    @Override  
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                             Bundle savedInstanceState) {  
        View view = inflater.inflate(R.layout.fragment, container, false);  
        //返回一个Unbinder值(进行解绑),注意这里的this不能使用getActivity()  
        unbinder = ButterKnife.bind(this, view);  
        return view;  
    }  

    /** 
     * onDestroyView中进行解绑操作 
     */  
    @Override  
    public void onDestroyView() {  
        super.onDestroyView();  
        unbinder.unbind();  
    }  
}  
  1. 在Adapter中绑定ButterKnife: 在Adapter的ViewHolder中使用,将ViewHolder加一个构造方法,在new ViewHolder的时候把view传递进去。使用ButterKnife.bind(this, view)进行绑定,代码如下:
代码语言:javascript
复制
public class MyAdapter extends BaseAdapter {  

  @Override   
  public View getView(int position, View view, ViewGroup parent) {  
    ViewHolder holder;  
    if (view != null) {  
      holder = (ViewHolder) view.getTag();  
    } else {  
      view = inflater.inflate(R.layout.testlayout, parent, false);  
      holder = new ViewHolder(view);  
      view.setTag(holder);  
    }  

    holder.name.setText("Donkor");  
    holder.job.setText("Android");
    // etc...  
    return view;  
  }  

  static class ViewHolder {  
    @BindView(R.id.title) TextView name;  
    @BindView(R.id.job) TextView job;  

    public ViewHolder(View view) {  
      ButterKnife.bind(this, view);  
    }  
  }  
}  
  1. 绑定View 控件id 注解: @BindView()
代码语言:javascript
复制
@BindView( R2.id.button)  
public Button button;  

布局内多个控件id 注解: @BindViews()

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    @BindViews({ R2.id.button1, R2.id.button2,  R2.id.button3})  
    public List<Button> buttonList ;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        ButterKnife.bind(this);  

        buttonList.get( 0 ).setText( "hello 1 ");  
        buttonList.get( 1 ).setText( "hello 2 ");  
        buttonList.get( 2 ).setText( "hello 3 ");  
    }  
}  
  1. 绑定资源 绑定string 字符串:@BindString()
代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    @BindView(R2.id.button) //绑定button 控件  
    public Button button ;  

    @BindString(R2.string.app_name)  //绑定资源文件中string字符串  
    String str;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        //绑定activity  
        ButterKnife.bind( this ) ;  
        button.setText( str );  
    }  
}  

绑定string里面array数组:@BindArray()

代码语言:javascript
复制
<resources>  
    <string name="app_name">城市</string>  

    <string-array name="city">  
        <item>北京市</item>  
        <item>天津市</item>  
        <item>哈尔滨市</item>  
        <item>大连市</item>  
        <item>香港市</item>  
    </string-array>  

</resources>  

------------------------------------------------------------------------------

public class MainActivity  extends AppCompatActivity {  

    @BindView(R2.id.button) //绑定button 控件  
    public Button button ;  

    @BindString(R2.string.app_name)  //绑定资源文件中string字符串  
    String str;  

    @BindArray(R2.array.city)  //绑定string里面array数组  
    String [] citys ;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        //绑定activity  
        ButterKnife.bind( this ) ;  
        button.setText(citys[0]);  
    }  
}  
  1. 绑定Bitmap 资源:@BindBitmap( )
代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    @BindView( R2.id.imageView ) //绑定ImageView 控件  
    public ImageView imageView ;  

    @BindBitmap( R2.mipmap.bm)//绑定Bitmap 资源  
    public Bitmap bitmap ;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        //绑定activity  
        ButterKnife.bind( this ) ;  

        imageView.setImageBitmap(bitmap);  
    }  

}  
  1. 绑定一个颜色值:@BindColor( )
代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    @BindView( R2.id.button)  //绑定一个控件  
    public Button button;  

    @BindColor( R2.color.colorAccent ) //具体色值在color文件中  
    int black ;  //绑定一个颜色值  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        //绑定activity  
        ButterKnife.bind( this ) ;  

        button.setTextColor(  black );  
    }  
}  
  1. 事件绑定 绑定点击事件:

绑定控件点击事件:@OnClick( )

绑定控件长按事件:@OnLongClick( )

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    @OnClick(R2.id.button1 )   //给 button1 设置一个点击事件  
    public void showToast(){  
        Toast.makeText(this, "is a click", Toast.LENGTH_SHORT).show();  
    }  

    @OnLongClick( R2.id.button1 )    //给 button1 设置一个长按事件  
    public boolean showToast2(){  
        Toast.makeText(this, "is a long click", Toast.LENGTH_SHORT).show();  
        return true ;  
    }  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        //绑定activity  
        ButterKnife.bind( this ) ;  
    }  
}  

指定多个id绑定事件:

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {  

    //Tip:当涉及绑定多个id事件时,我们可以使用Android studio的Butterknife
    //插件zelezny快速自动生成的,之后在下面会有介绍安装插件与使用  
    @OnClick({R.id.ll_product_name, R.id.ll_product_lilv, R.id.ll_product_qixian, R.id.ll_product_repayment_methods})  
    public void onViewClicked(View view) {  
        switch (view.getId()) {  
            case R.id.ll_product_name:  
                System.out.print("我是点击事件1");  
                break;  
            case R.id.ll_product_lilv:  
                System.out.print("我是点击事件2");  
                break;  
            case R.id.ll_product_qixian:  
                System.out.print("我是点击事件3");  

                break;  
            case R.id.ll_product_repayment_methods:  
                System.out.print("我是点击事件4");  
                break;  
        }  
    }  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        //绑定activity  
        ButterKnife.bind( this ) ;  
    }  
}

通过上面的例子可以看出多条点击事件是没有用R2的方式,如果一定要使用R2的写法,可以单一逐次写,正确的写法如下:

代码语言:javascript
复制
public class MainActivity extends AppCompatActivity {    

    @OnClick(R2.id.ll_product_name)    
    public void onViewClicked1(View view) {    
       System.out.print("我是点击事件1");               
    }    
    @OnClick(R2.id.ll_product_lilv)    
    public void onViewClicked2(View view) {    
       System.out.print("我是点击事件2");     
    }   
    @OnClick(R2.id.ll_product_qixian)    
    public void onViewClicked3(View view) {    
       System.out.print("我是点击事件3");               
    }    
    @OnClick(R2.id.ll_product_repayment_methods)    
    public void onViewClicked4(View view) {    
       System.out.print("我是点击事件4");               
    }    

    @Override    
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);    
        setContentView(R.layout.activity_main);    

        //绑定activity    
        ButterKnife.bind( this ) ;    
    }    
}    

自定义View使用绑定事件 不用指定id,直接注解OnClick。看代码觉得好像跟实现点击事件的方法类似。实际上并没有实现OnClickListener接口。代码如下:

代码语言:javascript
复制
public class MyButton extends Button {  
  @OnClick  
  public void onClick() {}  
}  

更多绑定注解:

代码语言:javascript
复制
@BindView—->绑定一个view;id为一个view 变量
@BindViews —-> 绑定多个view;id为一个view的list变量
@BindArray—-> 绑定string里面array数组;@BindArray(R.array.city ) String[] citys ;
@BindBitmap—->绑定图片资源为Bitmap;@BindBitmap( R.mipmap.wifi ) Bitmap bitmap;
@BindBool —->绑定boolean值
@BindColor —->绑定color;@BindColor(R.color.colorAccent) int black;
@BindDimen —->绑定Dimen;@BindDimen(R.dimen.borth_width) int mBorderWidth;
@BindDrawable —-> 绑定Drawable;@BindDrawable(R.drawable.test_pic) Drawable mTestPic;
@BindFloat —->绑定float
@BindInt —->绑定int
@BindString —->绑定一个String id为一个String变量;@BindString( R.string.app_name ) String meg;

更多事件注解:

代码语言:javascript
复制
@OnClick—->点击事件
@OnCheckedChanged —->选中,取消选中
@OnEditorAction —->软键盘的功能键
@OnFocusChange —->焦点改变
@OnItemClick item—->被点击(注意这里有坑,如果item里面有Button等这些有点击的控件事件的,需要设置这些控件属性focusable为false)
@OnItemLongClick item—->长按(返回真可以拦截onItemClick)
@OnItemSelected —->item被选择事件
@OnLongClick —->长按事件
@OnPageChange —->页面改变事件
@OnTextChanged —->EditText里面的文本变化事件
@OnTouch —->触摸事件
@Optional —->选择性注入,如果当前对象不存在,就会抛出一个异常,为了压制这个异常,可以在变量或者方法上加入一下注解,让注入变成选择性的,如果目标View存在,则注入, 不存在,则什么事情都不做
  1. ButterKnife的代码混淆 在混淆文件中,添加如下代码:
代码语言:javascript
复制
-keep class butterknife.** { *; }  
-dontwarn butterknife.internal.**  
-keep class **$$ViewBinder { *; }  

-keepclasseswithmembernames class * {  
    @butterknife.* <fields>;  
}  

-keepclasseswithmembernames class * {  
    @butterknife.* <methods>;  
}  

2.AbstractProcessor

ButterKnife中使用了注解的解析处理器AbstractProcessor。 AbstractProcessor,是一个抽象类,该类实现了接口Processor。 抽象类AbstractProcessor以及接口Processor都是位于包javax.annotation.processing中。

下面来说一下AbstractProcessor类中各方法的用法。

2.1 init

void init(ProcessingEnvironment processingEnv) ,该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类。

代码语言:javascript
复制
public interface ProcessingEnvironment {

        /**
         * 返回用来在元素上进行操作的某些实用工具方法的实现。<br>
         *
         * Elements是一个工具类,可以处理相关Element(包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
         */
        Elements getElementUtils();

        /**
         * 返回用来报告错误、警报和其他通知的 Messager。
         */
        Messager getMessager();

        /**
         *  用来创建新源、类或辅助文件的 Filer。
         */
        Filer getFiler();

        /**
         *  返回用来在类型上进行操作的某些实用工具方法的实现。
         */
        Types getTypeUtils();

        // 返回任何生成的源和类文件应该符合的源版本。
        SourceVersion getSourceVersion();

        // 返回当前语言环境;如果没有有效的语言环境,则返回 null。
        Locale getLocale();

        // 返回传递给注释处理工具的特定于 processor 的选项
        Map<String, String> getOptions();
    }

2.2 getSupportedSourceVersion

返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。

代码语言:javascript
复制
@Override
public SourceVersion getSupportedSourceVersion() {
  return SourceVersion.latestSupported();
}

2.3 getSupportedAnnotationTypes

返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。

2.4 process

注解处理器的核心方法,处理具体的注解,这个将在下面的自定义注解中详细说明该方法应该如何使用。

其中,getSupportedSourceVersion和getSupportedAnnotationTypes也可以通过给注解处理器添加注解指定具体值。 如下:

代码语言:javascript
复制
@SupportedOptions()
@SupportedAnnotationTypes()
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcessor extends AbstractProcessor {
    // 省略具体代码
}

3.ButterKnifeProcessor源码分析

3.1 ButterKnifeProcessor部分

ButterKnifeProcessor继承AbstractProcessor,复写了AbstractProcessor的一些方法

1.复写getSupportedAnnotations,添加声明的注解

代码语言:javascript
复制
 private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

2.复写getSupportedAnnotationTypes,返回支持的注解类型

代码语言:javascript
复制
 @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

3.重点是复写process方法

代码语言:javascript
复制
 @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;
  }

3.1.1ButterKnifeProcessor#findAndParseTargets

findAndParseTargets方法返回一个Map<TypeElement, BindingSet>,key是各种被注解声明的元素,value是BindingSet,这个类很重要,基本生成代码的逻辑都在其中,各个注解获取逻辑基本一致,我们就看一下最熟悉的BindViews

代码语言:javascript
复制
 // Process each @BindViews element.
    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindViews(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindViews.class, e);
      }
    }

1.遍历获取声明了BindViews的element,调用 parseBindViews方法进行解析

3.1.2 ButterKnifeProcessor#parseBindViews

代码语言:javascript
复制
private void parseBindViews(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    //关键1 
    boolean hasError = isInaccessibleViaGeneratedCode(BindViews.class, "fields", element)
        || isBindingInWrongPackage(BindViews.class, element);

    // Verify that the type is a List or an array.
    //关键2
    TypeMirror elementType = element.asType();
    String erasedType = doubleErasure(elementType);
    TypeMirror viewType = null;
    FieldCollectionViewBinding.Kind kind = null;
    if (elementType.getKind() == TypeKind.ARRAY) {
      ArrayType arrayType = (ArrayType) elementType;
      viewType = arrayType.getComponentType();
      kind = FieldCollectionViewBinding.Kind.ARRAY;
    } else if (LIST_TYPE.equals(erasedType)) {
      DeclaredType declaredType = (DeclaredType) elementType;
      List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
      if (typeArguments.size() != 1) {
        error(element, "@%s List must have a generic component. (%s.%s)",
            BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      } else {
        viewType = typeArguments.get(0);
      }
      kind = FieldCollectionViewBinding.Kind.LIST;
    } else {
      error(element, "@%s must be a List or array. (%s.%s)", BindViews.class.getSimpleName(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
    if (viewType != null && viewType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) viewType;
      viewType = typeVariable.getUpperBound();
    }

    // Verify that the target type extends from View.
    //关键3
    if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) {
      if (viewType.getKind() == TypeKind.ERROR) {
        note(element, "@%s List or array with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindViews.class.getSimpleName(), viewType, enclosingElement.getQualifiedName(),
            element.getSimpleName());
      } else {
        error(element, "@%s List or array type must extend from View or be an interface. (%s.%s)",
            BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      }
    }

    // Assemble information on the field.
    //关键4
    String name = element.getSimpleName().toString();
    int[] ids = element.getAnnotation(BindViews.class).value();
    if (ids.length == 0) {
      error(element, "@%s must specify at least one ID. (%s.%s)", BindViews.class.getSimpleName(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }
	//关键5
    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@%s annotation contains duplicate ID %d. (%s.%s)",
          BindViews.class.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    TypeName type = TypeName.get(requireNonNull(viewType));
    boolean required = isFieldRequired(element);
	//关键6
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    builder.addFieldCollection(new FieldCollectionViewBinding(name, type, requireNonNull(kind),
        new ArrayList<>(elementToIds(element, BindViews.class, ids).values()), required));

    erasedTargetNames.add(enclosingElement);
  }

1.通过isInaccessibleViaGeneratedCode方法验证被注解声明的类的合法性

代码语言:javascript
复制
  private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
      String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify field or method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
      error(element, "@%s %s must not be private or static. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify containing type.
    if (enclosingElement.getKind() != CLASS) {
      error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
      error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    return hasError;
  }

主要验证是否是私有的静态的,或者不是class文件,这些情况都抛异常

2.校验注解的元素是否是list或array

3.验证声明BindViews的类是继承自View

代码语言:javascript
复制
 if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) {
      if (viewType.getKind() == TypeKind.ERROR) {
        note(element, "@%s List or array with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindViews.class.getSimpleName(), viewType, enclosingElement.getQualifiedName(),
            element.getSimpleName());
      } else {
        error(element, "@%s List or array type must extend from View or be an interface. (%s.%s)",
            BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      }
    }

4.获取注解的值,就是R.id.xxx的数组,校验数组长度

代码语言:javascript
复制
  String name = element.getSimpleName().toString();
    int[] ids = element.getAnnotation(BindViews.class).value();
    if (ids.length == 0) {
      error(element, "@%s must specify at least one ID. (%s.%s)", BindViews.class.getSimpleName(),
          enclosingElement.getQualifiedName(), element.getSimpleName());
      hasError = true;
    }

5.通过findDuplicate方法寻找在一个注解上是否有重复的ID

代码语言:javascript
复制
  Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@%s annotation contains duplicate ID %d. (%s.%s)",
          BindViews.class.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

6.通过getOrCreateBindingBuilder方法生成BindingBuilder

3.1.3 ButterKnifeProcessor##getOrCreateBindingBuilder

代码语言:javascript
复制
private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }

通过 BindingSet.newBuilder,创建BindingSet.Builder,并添加到builderMap中缓存

BindingSet#newBuilder

代码语言:javascript
复制
 static Builder newBuilder(TypeElement enclosingElement) {
    // 返回此元素定义的类型。
    TypeMirror typeMirror = enclosingElement.asType();

    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }

    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
    //getModifiers()方法返回int类型值表示该字段的修饰符。
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

添加标识是否是View,activity, dialog,是否final修饰,这里是给被注解注入的类的名字添加_ViewBinding后缀

创建FieldCollectionViewBinding,添加到BindingSet.Builder,FieldCollectionViewBinding中包含了注解中声明的id数组

代码语言:javascript
复制
 BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    builder.addFieldCollection(new FieldCollectionViewBinding(name, type, requireNonNull(kind),
        new ArrayList<>(elementToIds(element, BindViews.class, ids).values()), required));

3.1.4 回到ButterKnifeProcessor#findAndParseTargets

代码语言:javascript
复制
//前面已经分析的代码省略
//.............
 Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();

      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          entries.addLast(entry);
        }
      }
    }

    return bindingMap;

经过一列操作,builderMap中已经存放了所有的注解和代码生成的BindingSet.Builder,然后通过builder.build(),生成BindingSet,将BindingSet调用put方法放回bindingMap中,这里还处理了有继承关系的类,最后bindingMap会返回。

这样findAndParseTargets方法的流程分析完了,然后回到ButterKnifeProcessor的process方法中

代码语言:javascript
复制
 @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;
  }

3.1.5 回到ButterKnifeProcessor#process

代码语言:javascript
复制
@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;
  }

遍历通过findAndParseTargets方法返回的bindingMap,调用BindingSet 的brewJava方法产生JavaFile

3.1.6 BindingSet#brewJava

代码语言:javascript
复制
JavaFile brewJava(int sdk, boolean debuggable) {
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

通过createType方法返回TypeSpec,这里的TypeSpec是square公司另一个开源框架javapoet的类,这个框架主要的作用就是生成java代码,关于javapoet的使用,可以自行上官网学习,然后生成JavaFile

3.1.7 BindingSet#createType

代码语言:javascript
复制
private TypeSpec createType(int sdk, boolean debuggable) {
    //创建一个类,且是plubic的,类名是bindingClassName(就是XXX_ViewBinding)
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    //如果有final修饰符则给该类增加
    if (isFinal) {
      result.addModifiers(FINAL);
    }
    //如果有父类,则添加继承关系
    if (parentBinding != null) {
      result.superclass(parentBinding.bindingClassName);
    } else {
      //否则实现Unbinder接口
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      //添加私有成员变量target,类型就是对应的使用了butterKnife注解的类
      result.addField(targetTypeName, "target", PRIVATE);
    }
    //添加构造方法,对于isView,isActivity,isDialog有不同的处理
    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.
      //如果构造函数需要view的,生成传入view的构造方法
      result.addMethod(createBindingViewDelegateConstructor());
    }
    //生成_ViewBinding的构造函数
    result.addMethod(createBindingConstructor(sdk, debuggable));

    //生成unbind方法
    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
  }

javaFile#writeTo

代码语言:javascript
复制
  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());
      }
    }

javaFile.writeTo就是生成代码的逻辑了,这些属于javapoet代码,不再做分析,至此ButterKnifeProcessor分析完毕

4.ButterKnife.bind源码分析

这里仅仅拿activity中ButterKnife.bind(this)分析

代码语言:javascript
复制
 @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

获取DecorView,调用createBinding

代码语言:javascript
复制
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    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);
    } 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);
    }
  }

1.通过findBindingConstructorForClass方法返回bind类的对应_ViewBinding的构造函数,findBindingConstructorForClass中会使用classloader去加载_ViewBinding类的class文件,然后返回其构造函数

2.通过constructor.newInstance(target, source)方法,创建出_ViewBinding类的实体,这里的target的就是声明了注解的类,这样_ViewBinding就持有了target的引用,剩下的findViewById还有各种设置点击的方法是在_ViewBinding中实现的

这里我仅仅拿一个我写的简单的类进行分析,下面是我写的代码 ButterKnifeActivity.java

代码语言:javascript
复制
public class ButterKnifeActivity extends Activity {
    @BindView(R.id.bt1)
    public Button mBt1;
    @BindView(R.id.bt2)
    public Button mBt2;

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

    @OnClick(R.id.bt1)
    public void test1() {
        Log.d("myl", "bt1 click");

    }

    @OnClick(R.id.bt2)
    public void test2(){
        Log.d("myl", "bt2 click");
    }

}

生成的ButterKnifeActivity_ViewBinding.java

代码语言:javascript
复制
public class ButterKnifeActivity_ViewBinding implements Unbinder {
  private ButterKnifeActivity target;

  private View view2131165234;

  private View view2131165235;

  @UiThread
  public ButterKnifeActivity_ViewBinding(ButterKnifeActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.bt1, "field 'mBt1' and method 'test1'");
    target.mBt1 = Utils.castView(view, R.id.bt1, "field 'mBt1'", Button.class);
    view2131165234 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.test1();
      }
    });
    view = Utils.findRequiredView(source, R.id.bt2, "field 'mBt2' and method 'test2'");
    target.mBt2 = Utils.castView(view, R.id.bt2, "field 'mBt2'", Button.class);
    view2131165235 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.test2();
      }
    });
  }

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

    target.mBt1 = null;
    target.mBt2 = null;

    view2131165234.setOnClickListener(null);
    view2131165234 = null;
    view2131165235.setOnClickListener(null);
    view2131165235 = null;
  }
}

调用了ButterKnifeActivity_ViewBinding构造方法之后就进入如下代码:

代码语言:javascript
复制
  @UiThread
  public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.bt1, "field 'mBt1' and method 'test1'");
    target.mBt1 = Utils.castView(view, R.id.bt1, "field 'mBt1'", Button.class);
    view2131165234 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.test1();
      }
    });
    view = Utils.findRequiredView(source, R.id.bt2, "field 'mBt2' and method 'test2'");
    target.mBt2 = Utils.castView(view, R.id.bt2, "field 'mBt2'", Button.class);
    view2131165235 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.test2();
      }
    });
  }

1.通过Utils.findRequiredView方法找到对应id的view,里面就是调用了构造函数传进来的source的findViewById

代码语言:javascript
复制
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.");
  }

2.调用Utils.castView将找到的View进行强转然后进行赋值给target,所以我们明白了为什么BindView的注解的类必须是public的

3.如果有标记@OnClick注解,则对view进行点击事件设置

到这里,ButterKnife的源码分析流程基本结束,如有不对的地方,可以指出校正。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.ButterKnife的使用
    • 1.1 如何接入
      • 1.2 ButterKnife使用心得与注意事项
        • 1.3 ButterKnife基本使用
        • 2.AbstractProcessor
          • 2.1 init
            • 2.2 getSupportedSourceVersion
              • 2.3 getSupportedAnnotationTypes
                • 2.4 process
                • 3.ButterKnifeProcessor源码分析
                  • 3.1 ButterKnifeProcessor部分
                    • 3.1.1ButterKnifeProcessor#findAndParseTargets
                      • 3.1.2 ButterKnifeProcessor#parseBindViews
                        • 3.1.3 ButterKnifeProcessor##getOrCreateBindingBuilder
                          • 3.1.4 回到ButterKnifeProcessor#findAndParseTargets
                            • 3.1.5 回到ButterKnifeProcessor#process
                              • 3.1.6 BindingSet#brewJava
                                • 3.1.7 BindingSet#createType
                                  • javaFile#writeTo
                                  • 4.ButterKnife.bind源码分析
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档