Every minute with you read ButterKnife source

Why write this series of blog it?

Because in the process of development of Android, generics, reflection, knowledge comes into play these notes will be used, at least almost all of the framework will be used in the above twelve kinds of knowledge, such as Gson to use generics, reflection, annotations, Retrofit also used generics, reflection, annotations. This knowledge is very important to learn our advanced, especially reading the source code or open-source framework to develop their own open-source framework.

Foreword

ButterKnife this open source library fire for some time, the beginning of its implementation principle is to use reflection to achieve poor performance. Back then releases gradually achieved using annotations + radiation, performance improved a lot.

ButterKnife framework is based on compile-time, it can help us to subtract each write FindViewById of trouble, as of 2017.5.1, start at github above has more than 15,000.

ButterKnife source of this blog to be analyzed include the following three parts, the version number is 8.5.1

  • butterknife-annotations
  • butterknife-compiler
  • butterknife

Wherein butterknife-annotations database mainly used to store custom annotations; butterknife-compiler used to scan which is mainly used to place our custom annotations, and the corresponding process, generating template code like; butterknife is mainly used to inject our code.

Let's take a first look at how to use butterknife:

The basic use of ButterKnife

The increased reliance on build.gradle moudle

dependencies {
  compile 'com.jakewharton:butterknife:8.5.1'
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}

  

public class SimpleActivity extends Activity {
  private static final ButterKnife.Action<View> ALPHA_FADE = new ButterKnife.Action<View>() {
    @Override public void apply(@NonNull View view, int index) {
      AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
      alphaAnimation.setFillBefore(true);
      alphaAnimation.setDuration(500);
      alphaAnimation.setStartOffset(index * 100);
      view.startAnimation(alphaAnimation);
    }
  };

  @BindView(R2.id.title) TextView title;
  @BindView(R2.id.subtitle) TextView subtitle;
  @BindView(R2.id.hello) Button hello;
  @BindView(R2.id.list_of_things) ListView listOfThings;
  @BindView(R2.id.footer) TextView footer;

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

  private SimpleAdapter adapter;

  @OnClick(R2.id.hello) void sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
    ButterKnife.apply(headerViews, ALPHA_FADE);
  }

  @OnLongClick(R2.id.hello) boolean sayGetOffMe() {
    Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();
    return true;
  }

  @OnItemClick(R2.id.list_of_things) void onItemClick(int position) {
    Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show();
  }

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

    // Contrived code to use the bound fields.
    title.setText("Butter Knife");
    subtitle.setText("Field and method binding for Android views.");
    footer.setText("by Jake Wharton");
    hello.setText("Say Hello");

    adapter = new SimpleAdapter(this);
    listOfThings.setAdapter(adapter);
  }
}

  

Call gradle build command, we will see in the corresponding directory generate code like this.

public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
  protected T target;

  private View view2130968578;

  private View view2130968579;

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

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

    target.title = null;
    target.subtitle = null;
    target.hello = null;
    target.listOfThings = null;
    target.footer = null;
    target.headerViews = null;

    view2130968578.setOnClickListener (zero);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}

  

ButterKnife implementation process

In general, it can be divided into the following steps:

  • At compile time scanning notes, and make the appropriate treatment, generate java code, generate Java code is generated to call javapoet library.
  • When we call ButterKnife.bind (this); methods when he will fully qualified according to the type of class, find the corresponding code, and execute it. And completion findViewById setOnClick, setOnLongClick other operations.

The first step: at compile time scanning notes, and do the appropriate processing to generate java code. This step can be split into several small steps:

  • Define our notes, we declare the annotation whether to save the java doc, you can act on what area (Filed, Class, etc.), and is annotating source, compile-time or run-time annotation notes, etc.)
  • Inheritance AbstractProcessor, indicate which type of annotation support, which supports version,
  • The method of rewriting process, processing related annotations Map deposited into the collection
  • According to the scanned notes information (ie Map collection), call javapoet library generated Java code.
butterknife-annotations to explain
 

 

We know ButterKnife customize many notes, there BindArray, BindBitmap, BindColor, BindView and so, here we BindView as an example to explain the OK, the other is substantially similar, there is no longer explained.

// compile-time annotation 
@Retention (the CLASS) 
// member variables, (Includes enum Constants) 
@Target (the FIELD) 
public @interface BindView { 
  / ** Which to View ID at The Field, bound by Will BE. * / 
  @IdRes int value ( ); 
}

  

Processor parser Description

Let's look at some of the basic methods: init method in which some auxiliary tools, so there is a benefit, to ensure that tools are singletons, because the init method is only called during initialization.

public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    ---
   
    //辅助工具类
    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
   
   ---
}

  

Then rewrite getSupportedAnnotationTypes method, return annotation types we support.

@Override
public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
        types.add(annotation.getCanonicalName());
    }
    //返回支持注解的类型
    return types;
}

private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

    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(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
}

  

Next, look at our focus, process approach. Probably the work done is to get all of our annotation information, deposited into a collection of map, traverse the map collection, do the appropriate processing to generate java code.

@Override 
public boolean Process (<? The extends TypeElement> the Set Elements, The RoundEnvironment that env) { 
    // get all the notes information, TypeElement as a key, BindingSet as value 
    the Map <TypeElement, BindingSet> bindingMap = findAndParseTargets (env); 
    // iterate All inside map information, and generates java code 
    for (of Map.Entry <a TypeElement, BindingSet> entry: bindingMap.entrySet ()) { 
        a TypeElement TypeElement entry.getKey = (); 
        BindingSet entry.getValue Binding = (); 

        javaFile javaFile = binding.brewJava (SDK); 
        the try { 
            javaFile.writeTo (Filer); 
        } the catch (IOException E) { 
            error (TypeElement, "Unable to Write Binding% for type S:% S", TypeElement, E
                    .getMessage());
        }
    }

    return false;
}

  

Here we enter findAndParseTargets method, which in the end is to see how the annotation information is stored into the map collection?

findAndParseTargets for each method inside a custom annotation (BindArray, BindBitmap, BindColor, BindView) so do the deal, here we focus on the process can be @BindView. Other notes of thought process is the same.

Let's look at the first half of findAndParseTargets method of traversing env.getElementsAnnotatedWith (BindView.class) collection, and call parseBindView approach to transformation.

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

   
    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
        // we don't SuperficialValidation.validateElement(element)
        // so that an unresolved View type can be generated by later processing rounds
        try {
            parseBindView(element, builderMap, erasedTargetNames);
        } catch (Exception e) {
            logParsingError(element, BindView.class, e);
        } 
    } 
    
    --- 
    
    // the second half, when will revisit 

}

  

You can see the main logic catching portion inside parseBindView method, mainly to do the following operation steps:

  • Judge annotated @BindView modified member variable is not legal, private or static modification, it is wrong.
void parseBindView Private (the Element Element, the Map <a TypeElement, BindingSet.Builder> builderMap, 
                           the Set <a TypeElement> erasedTargetNames) { 
    a TypeElement enclosingElement = (a TypeElement) element.getEnclosingElement (); 

    if judged to be annotated on // property, if the property is is private or static modification, the error 
    // determine whether annotated in the wrong package, if the package name begins with "android" or "java", the error 
    boolean hasError = isInaccessibleViaGeneratedCode (BindView.class, " fields", element ) 
            || isBindingInWrongPackage (BindView.class, Element); 

    // The target type that the Verify the extends from View. 
    TypeMirror elementType is element.asType = (); 
    IF (elementType.getKind () == TypeKind.TYPEVAR) { 
        of TypeVariable of TypeVariable = ( TypeVariable) elementType;
        elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    // 判断元素是不是View及其子类或者Interface
    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 (), the qualifiedName, SimpleName); 
            hasError = to true; 
        } 
    } 
    // if there is an error, direct return 
    IF (hasError) { 
        return; 
    } 

    // ON Assemble The Information Field. 
    Int ID = element.getAnnotation ( BindView.class) .Value (); 
    // the earth element to find where the builder 
    BindingSet.Builder = builderMap.get builder (enclosingElement); 
    QualifiedId qualifiedId = elementToQualifiedId (element, ID); 
    // if appropriate builder already exists 
    if (Builder = null!) { 
        // verify that the ID has been bound 
        String existingBindingName = builder.findExistingBindingName (getId (qualifiedId ));
        // was bound, errors, returned 
        IF (existingBindingName! = Null) { 
            error (Element, "Attempt to use S @% AN already bound for D% ID ON '% S'. (% S.% S)" , 
                    BindView.class.getSimpleName (), ID, existingBindingName, 
                    enclosingElement.getQualifiedName (), element.getSimpleName ()); 
            return; 
        } 
    } the else { 
        // if no corresponding builder, it is necessary to regenerate, and do not store the builderMap the 
        Builder = getOrCreateBindingBuilder (builderMap, enclosingElement); 
    } 

    String name = simpleName.toString (); 
    the TypeName = TypeName.get type (elementType is); 
    Boolean isFieldRequired required = (Element);

    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

  

After parseBindView method for the analysis is complete, we look at the second half findAndParseTargets method, the main work is done bindingMap reorder in looking back.

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {

    // 省略前半部分
       
    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
    // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    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();
        // Get the type of the parent class TypeElement 
        TypeElement parentType = findParentType (type, erasedTargetNames); 
        // null, stored into Map 
        IF (parentType == null) { 
            bindingMap.put (type, builder.build ()); 
        } the else { 
             // Get the parentType BindingSet 
            BindingSet parentBinding = bindingMap.get (parentType); 
            IF (parentBinding = null!) { 
                builder.setParent (parentBinding); 
                bindingMap.put (type, builder.build ()); 
            } the else { 
                // But the superclass a Binding WE Has Not have have yet Built IT. Re-the enqueue for later. 
                // empty, added to the end of the queue, waiting for the first treatment
                entries.addLast(entry);
            }
        }
    }

    return bindingMap;
}

  

Up to this point, we have analyzed how the process is finished ButterKnifeProcessor knowledge annotation, save in the map collection, let's return to the process method, is to look at how to generate java template code.

Process Boolean public (<? a TypeElement the extends> the Set Elements, The RoundEnvironment that the env) { 
    // get information of all annotations, TypeElement as a key, BindingSet as value 
    the Map <a TypeElement, BindingSet> bindingMap = findAndParseTargets (the env); 
    // iterate map inside all the information, and generates java code 
    for (of Map.Entry <a TypeElement, BindingSet> entry: bindingMap.entrySet ()) { 
        a TypeElement TypeElement entry.getKey = (); 
        BindingSet entry.getValue Binding = (); 
         // objects generated javaFile 
        javaFile = binding.brewJava javaFile (SDK); 
        the try { 
             // java generating template code               
            javaFile.writeTo (Filer); 
        } the catch (IOException E) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, e
                    .getMessage());
        }
    }

    return false;
}

  

Code generation core code only these few lines

// generates javaFile objects 
JavaFile javaFile = binding.brewJava (SDK); 
the try { 
     // java generating template code 
    javaFile.writeTo (Filer); 
} the catch (IOException E) { 
    error (TypeElement, "Unable to Write Binding% for type S :% S ", TypeElement, E 
            .getMessage ()); 
}

  

Tracking in, we found the company is calling square open source library  javapoet  open the generated code. Use on javaPoet can refer to the official website address

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

private TypeSpec createType(int sdk) {
    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 it is or is a subclass of View View, then adding the constructor 
    IF (isView) { 
        result.addMethod (createBindingConstructorForView ()); 
    } the else IF (isActivity) {// If Activity Activity or subclass, then adding the constructor 
        result.addMethod (createBindingConstructorForActivity ()); 
    } the else IF (isDialog) {// If Dialog or subclass Dialog then added constructor 
        result .addMethod (createBindingConstructorForDialog ()); 
    } 
    // If the constructor does not require parameters View, View need to add constructor parameter 
    IF (constructorNeedsView ()!) { 
        // constructor the Delegating with the Add a + View Signature a target type for use Reflective .
        result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk));

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

    return result.build();
}

  

Then we look together things createBindingConstructor (sdk) method, probably do is

  • Set up listeners to determine whether there is, if there is listening, the View is set to final
  • Traversal viewBindings, call addViewBinding findViewById generated form code.
MethodSpec createBindingConstructor Private (int SDK) { 
    MethodSpec.Builder MethodSpec.constructorBuilder = constructor () 
            .addAnnotation (UI_THREAD) 
            .addModifiers (the PUBLIC); 
    // if binding methods, such as @onClick, then add a method of the type parameter target targetTypeName , and is the type of final 
    IF (hasMethodBindings ()) { 
        constructor.addParameter (targetTypeName, "target", fINAL); 
    } the else {// If not, not final type 
        constructor.addParameter (targetTypeName, "target"); 
    } 
    // If annotated View, then add the source type parameter VIEW 
    IF (constructorNeedsView ()) { 
        constructor.addParameter (VIEW, "source"); 
    } the else { 
        // add context context parameter type
        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());
    }
    // 如果 @OnTouch 绑定 View,添加 @SuppressLint("ClickableViewAccessibility")
    if (hasOnTouchMethodBindings()) {
        constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
                .addMember("value", "$S", "ClickableViewAccessibility")
                .build());
    }
    // 如果 parentBinding 不为空,调用父类 的构造方法
    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);
        }
        //   遍历  viewBindings,生成  source.findViewById($L) 代码
        for (ViewBinding binding : viewBindings) {
            addViewBinding(constructor, binding);
        }
        for (FieldCollectionViewBinding binding : collectionBindings) {
            constructor.addStatement("$L", binding.render());
        }

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

  

Here we take a look at addViewBinding method is how to generate the code.

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
    if (binding.isSingleFieldBinding()) {
        // Optimize the common case where there's a single binding directly to a field.
        FieldViewBinding fieldBinding = binding.getFieldBinding();
        // 注意这里直接使用了 target. 的形式,所以属性肯定是不能 private 的
        CodeBlock.Builder builder = CodeBlock.builder()
                .add("target.$L = ", fieldBinding.getName());

        boolean requiresCast = requiresCast(fieldBinding.getType());
        if (!requiresCast && !fieldBinding.isRequired()) {
            builder.add("source.findViewById($L)", binding.getId().code);
        } else {
            builder.add("$T.find", UTILS);
            builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
            if (requiresCast) {
                builder.add("AsType");
            }
            builder.add("(source, $L", binding.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;
    }

  

ButterKnife is how to achieve code injection

Used ButterKnife get people basically know that we bind method is achieved by injection, that automatically help us findViewById, the liberation of our hands, improve work efficiency. Here we take a look at how to achieve the bind method is injection.

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

  

We can see the bind method is very simple, basic logic to createBinding method to complete. We entered together createBinding method look in the end what has been done.

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());
    // 从 Class 中查找 constructor
    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);
    }
}

  

CreateBinding In fact, the main job of a few things this

  • Incoming class, instantiated by a constructor method findBindingConstructorForClass
  • Constructor to initialize the object using the reflection
  • Failed to initialize constructor throws an exception

Here we take a look at findBindingConstructorForClass method is how to achieve.

the Constructor findBindingConstructorForClass static Private (CLS Class <?>) {<the extends Unbinder?> 
    // read cache, if not empty, return directly to 
    the Constructor bindingCtor = BINDINGS.get (CLS) <the extends Unbinder?>; 
    IF (bindingCtor =! null) { 
        IF (Debug) Log.d (the TAG, "the HIT: Binding in Cached Map."); 
        return bindingCtor; 
    } 
    // if android, java native document does not deal 
    String clsName = cls.getName (); 
    IF (clsName.startsWith (. "Android") || clsName.startsWith (. "Java")) { 
        IF (Debug) Log.d (the TAG,. "MISS: Abandoning Search Reached Framework class."); 
        return null; 
    } 
    the try {
        <?> Class bindingClass = cls.getClassLoader () loadClass (clsName + "_ViewBinding");. 
        // noinspection unchecked 
        // Find the class where the original 
        bindingCtor = (<? Extends Unbinder> Constructor) bindingClass.getConstructor (cls, View 
                .class); 
        IF (Debug) Log.d (the TAG, "the HIT:. Loaded Binding and class constructor"); 
    } the catch (a ClassNotFoundException E) { 
        IF (Debug) Log.d (the TAG, "Not found the Trying the superclass." cls.getSuperclass + () getName ());. 
        // find the original class, can not find, the parent to find 
        bindingCtor = findBindingConstructorForClass (cls.getSuperclass ()); 
    } the catch (a NoSuchMethodException E) {
        throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    // 存进 LinkedHashMap 缓存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

  

Its idea is to achieve this:

  • Read cache, if the cache hit, direct return, this will help improve efficiency. Can be seen from the code buffer is stored into the map set by the implemented.
  • Are we target file is then processed, it is not, directly back, and print the appropriate log
  • The use of our own class loader loads generated class file and get its constructor, to get a direct return. Not obtain an exception is thrown, the exception handling, we have to find another parent from the current class file. And the result is stored into the map collection, do caching.

Our analysis of ButterKnife stop here.

Digression

This blog is to analyze the main principle ButterKnife implementation of ButterKnife inside some of the implementation details have not analyzed in detail. But for us to read the code enough. The next series, mainly on the realization of the principle CoordinatorLayout and how to customize the behavior CoordinatorLayout achieve imitation Weibo found that the effect of the page, so stay tuned.

 

 

about me

For more information you can click on me  , and very much like to share with everyone, and common progress

Guess you like

Origin www.cnblogs.com/1157760522ch/p/11646794.html