Inventory common Android development libraries (2) - ButterKnife

I. Introduction

Butterknife is a widely used platform andoid dependency injection framework, using Butterknife avoid a lot of findViewById, setOnClickListener etc. Find View, binding code of events, but also had no effect on application performance.

Second, the use

2.1 Add dependence

  • Add the following code in the Project's build.gradle
buildscript {
    repositories {
        ...
    }
    dependencies {
        ...
        classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'    //添加这一行代码
    }
}
  • App add the following code in the build.gradle
    //ButterKnife
    implementation 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'

2.2 bindings and annotations

  • Activity in the use ButterKnife

Use ButterKnife.bind (this) to bind, attention must be called after setContentView method. Use @BindView (@ResId) Binding View control, use @OnClick (@ResId) bind click event.

public class ButterKnifeActivity extends AppCompatActivity {

    @BindView(R.id.text_view01)
    TextView mTextView01;
    @BindView(R.id.button01)
    Button mButton01;

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

    @OnClick(R.id.button01)
    public void onViewClicked() {
        
    }
}
  • Use ButterKnife in the Fragment

Use ButterKnife.bind (this, view) binding in onCreateView, and the need for unbundling in onDestroyView, the binding mode View control and events with Activity.

public class ButterKnifeFragment extends Fragment {
    @BindView(R.id.text_view01)
    TextView mTextView01;
    @BindView(R.id.button01)
    Button mButton01;
    
    private Unbinder mUnbinder;
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_butter_knife,container,false);
        mUnbinder = ButterKnife.bind(this,view);
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mUnbinder.unbind();
    }

    @OnClick(R.id.button01)
    public void onViewClicked() {

    }
}
  • In the Adapter in use ButterKnife

ViewHolder inner class defined in the adapter, and the binding ButterKnife in ViewHolder.

    public class ViewHolder {
        @BindView(R.id.text_view01)
        TextView mTextView01;
        
        public ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }
  • View, resources, event binding
    /*************  绑定View *************/
    //单个View绑定
    @BindView(R.id.text_view)  
    public TextView mTextView;
    
    //多个View绑定
    @BindViews({R.id.text_view01,R.id.text_view02})
    public TextView mTextView01,mTextView02;
    @BindViews({ R.id.button1, R.id.button2,  R.id.button3})
    public List<Button> buttonList ;

    /*************  绑定资源 *************/
    //String字符串
    @BindString(R.string.app_name)
    String app_name;
    
    //string里面array数组
    @BindArray(R.array.name)  
    String [] names ;
    
    //mipmap里面的Bitmap资源
    @BindBitmap(R.mipmap.ic_launcher)
    Bitmap mBitmap;

    //具体色值在color文件中
    @BindColor( R.color.colorAccent ) 
    int black ;

    /*************  绑定事件 *************/
    //onClick事件
    @OnClick(R.id.button01)     
    public void onClick(View view){ 
        
    }
    
    //多个onClick事件绑定
    @OnClick({R.id.button01,R.id.button02})
    public void onViewsClick(View view){
        
    }
    
    //onLongClick事件
    @OnLongClick(R.id.button01)
    public void onLongClick(View view){
        
    }

These are commonly used binding way, there are other less common View, resources, event binding will not enumerate here.

Third, analysis

ButterKnife very easy to use, so how about the source of it? To analyze the following analysis:

ButterKnife.bind method is a series of overloaded methods, calling different bind according to different scenarios, eventually calling a unified approach.

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

  ...

  @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return bind(target, target);
  }

  ...

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

  ...

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

  ...

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

  ...

  @NonNull @UiThread
  public static Unbinder bind(@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);
    }
  }

We direct analysis of the final bind method on the line, which was first acquire the target class, then get a Constructor by findBindingConstructorForClass method, the final call Constructor.newInstance () returns the inheritance Unbinder generics.

findBindingConstructorForClass follows:

  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    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 {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      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;
  }

 First look in the cache whether there is a corresponding template class, if there is no new template class is loaded by reflection (named for the template class: class name + _viewBinding), and then get its constructor, and finally stored in the cache and returned.

Through the above analysis, we know that every bind the ButterKnife will generate a corresponding template class, then this class in place? When compiling a file, the corresponding template class is generated in the app> build> generated> source> apt medium.

Generating a corresponding class template compile

 We look template class generated:

public class ButterKnifeActivity_ViewBinding implements Unbinder {
  private ButterKnifeActivity target;

  private View view7f070022;

  @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;
    target.mTextView01 = Utils.findRequiredViewAsType(source, R.id.text_view01, "field 'mTextView01'", TextView.class);
    view = Utils.findRequiredView(source, R.id.button01, "field 'mButton01' and method 'onViewClicked'");
    target.mButton01 = Utils.castView(view, R.id.button01, "field 'mButton01'", Button.class);
    view7f070022 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onViewClicked();
      }
    });
  }

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

    target.mTextView01 = null;
    target.mButton01 = null;

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

In the constructor of the target class can be seen in View by findRequiredViewAsType acquired in findRequiredViewAsType the method is called findRequiredView.

  public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
  }

  ...

  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.");
  }

Ultimately, we see the familiar findViewById. The binding event is called directly in the constructor method to get findRequiredView View, and then bind the corresponding event directly in the constructor method of the target class notes and the corresponding callback.

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

    View view;
    view = Utils.findRequiredView(source, R.id.button01, "field 'mButton01' and method 'onViewClicked'");
    target.mButton01 = Utils.castView(view, R.id.button01, "field 'mButton01'", Button.class);
    view7f070022 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onViewClicked();
      }
    });
  }

Fourth, reference links

butterknife source code analysis & simple principles outlined

ButterKnife官网

ButterKnife source parsing

Published 10 original articles · won praise 3 · Views 456

Guess you like

Origin blog.csdn.net/ledding/article/details/100517719