butterknife源码详解

butterknife 源码分析 butterknife 主要是用来注入我们的代码的。我们知道 ButterKnife 自定义很多的注解,有 BindArray,BindBitmap,BindColor,BindView 等,这里我们以 BindView 为例子讲解就 OK 了,其他的也是基本类似的,这里就不再讲解了。方法的时候,他会根据类的全限定类型,找到相应的代码,并执行。定义我们的注解,声明我们的注解是否保存到 java doc 中,可以作用于哪些区域(Filed ,Class等),以及是源码时注解,编译时注解还是运行时注解等) 阅读详情

butterknife源码详解

作为Android开发者,大家肯定都知道大名鼎鼎的butterknife。它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码。最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理。

如果你之前不了解Annotation,那强烈建议你先看注解使用.

废多看图:

image

从图中可以很直观的看出它的module结构,以及使用示例代码。

它的目录和我们在注解使用这篇文章中介绍的一样,大体也是分为三个部分:

  • app : butterknife
  • api : butterknife-annotations
  • compiler : butterknife-compiler

通过示例代码我们大体能预料到对应的功能实现:

  • @BindView(R2.id.hello) Button hello;
    BindView注解的作用就是通过value指定的值然后去调用findViewById()来找到对应的控件,然后将该控件赋值给使用该注解的变量。

  • @OnClick(R2.id.hello) void sayHello() {...}
    OnClick注解也是通过指定的id来找到对应控件后,然后对其设置onClickListener并调用使用该注解的方法。

  • 最后不要忘了ButterKnife.bind(this);该方法也是后面我们要分析的突破点。

当然Butterknife的功能是非常强大的,我们在这里只是用这两个简单的例子来进行分析说明。

那我们就来查看BindView和Onclik注解的源码:

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

作用在变量上的编译时注解。对该注解的值value()使用android.support.annotation中的IdRes注解,来表明该值只能是资源类型的id。

@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

作用到方法上的编译时注解。我们发现该注解还使用了ListenerClass注解,当然从上面的声明中可以很容易看出它的作用。
那我们就继续简单的看一下
ListenerClass`注解的实现:

@Retention(RUNTIME) @Target(ANNOTATION_TYPE)
public @interface ListenerClass {
  String targetType();

  /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */
  String setter();

  /**
   * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If
   * empty {@link #setter()} will be used by default.
   */
  String remover() default "";

  /** Fully-qualified class name of the listener type. */
  String type();

  /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
  Class<? extends Enum<?>> callbacks() default NONE.class;

  /**
   * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}
   * and an error to specify more than one value.
   */
  ListenerMethod[] method() default { };

  /** Default value for {@link #callbacks()}. */
  enum NONE { }
}

作用到注解类型的运行时注解。

有了之前注解使用这篇文章的基础,我们知道对于编译时注解肯定是要通过自定义AbstractProcessor来解析的,所以接下来我们要去butterknife-compiler module中找一下对应的类。通过名字我们就能很简单的找到:

package butterknife.compiler;

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
   ...
}

通过AutoService注解我们很容易看出来Butterknife也使用了Google Auto。当然它肯定也都用了javaopet和android-apt,这里我们就不去分析了。
其他的一些方法我们就不继续看了,我们接下来看一下具体的核心处理方法,也就是ButterKnifeProcessor.process()方法:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    // 查找、解析出所有的注解
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    // 将注解后要生成的相关代码信息保存到BindingClass类中
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();
      // 输出生成的类
      for (JavaFile javaFile : bindingClass.brewJava()) {
        try {
          javaFile.writeTo(filer);
        } catch (IOException e) {
          error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
              e.getMessage());
        }
      }
    }

    return true;
  }

从process()方法来看,我们需要主要分析两个部分:

  • findAndParseTargets():查找、解析所有的注解
  • bindingClass.brewJava():生成代码
第一步:findAndParseTargets()

先查看findAndParseTargets()方法的实现,里面解析的类型比较多,我们就以BindView为例进行说明:

private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

    // Process each @BindArray element.
    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceArray(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindArray.class, e);
      }
    }

    // Process each @BindBitmap element.
    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBitmap(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBitmap.class, e);
      }
    }

    // Process each @BindBool element.
    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBool(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBool.class, e);
      }
    }

    // Process each @BindColor element.
    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceColor(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindColor.class, e);
      }
    }

    // Process each @BindDimen element.
    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDimen(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDimen.class, e);
      }
    }

    // Process each @BindDrawable element.
    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDrawable(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDrawable.class, e);
      }
    }

    // Process each @BindInt element.
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }

    // Process each @BindString element.
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.class, e);
      }
    }

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // 检查一下合法性
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        // 进行解析 
        parseBindView(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

    // Process each @BindViews element.
    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseBindViews(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindViews.class, e);
      }
    }

    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
    }

    // Try to find a parent binder for each.
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
      if (parentType != null) {
        BindingClass bindingClass = entry.getValue();
        BindingClass parentBindingClass = targetClassMap.get(parentType);
        bindingClass.setParent(parentBindingClass);
      }
    }

    return targetClassMap;
  }

继续看一下parseBindView()方法:

private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

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

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    // 必须是View类型或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
          BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }
    // 通过注解的value拿到id
    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass != null) {
      // 之前已经绑定过该id
      ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
      if (viewBindings != null && viewBindings.getFieldBinding() != null) {
        FieldViewBinding existingBinding = viewBindings.getFieldBinding();
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBinding.getName(),
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      // 没有绑定过该id的话就去生成代码
      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    }

    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    FieldViewBinding binding = new FieldViewBinding(name, type, required);
    // 用BindingClass添加代码
    bindingClass.addField(getId(id), binding);

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

终于进入生成代码的阶段了,继续看一下getOrCreateTargetClass()的实现:

private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
      TypeElement enclosingElement) {
    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass == null) {
      TypeName targetType = TypeName.get(enclosingElement.asType());
      if (targetType instanceof ParameterizedTypeName) {
        targetType = ((ParameterizedTypeName) targetType).rawType;
      }
      // 得到包名、类名
      String packageName = getPackageName(enclosingElement);
      String className = getClassName(enclosingElement, packageName);
      // 用包名、类名和_ViewBinder等拼接成要生成的类的全名,这里会有两个类:$$_ViewBinder和$$_ViewBinding
      ClassName binderClassName = ClassName.get(packageName, className + "_ViewBinder");
      ClassName unbinderClassName = ClassName.get(packageName, className + "_ViewBinding");

      boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
      // 将要生成的类名,$$_ViewBinder和$$_ViewBinding封装给BindingClass类
      bindingClass = new BindingClass(targetType, binderClassName, unbinderClassName, isFinal);
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

继续看一下BindingClass.addField():

void addField(Id id, FieldViewBinding binding) {
    getOrCreateViewBindings(id).setFieldBinding(binding);
  }

继续看getOrCreateViewBindings()以及setFieldBinding()方法:

private ViewBindings getOrCreateViewBindings(Id id) {
    ViewBindings viewId = viewIdMap.get(id);
    if (viewId == null) {
      viewId = new ViewBindings(id);
      viewIdMap.put(id, viewId);
    }
    return viewId;
  }

然后看ViewBindings.setFieldBinding()方法:

public void setFieldBinding(FieldViewBinding fieldBinding) {
    if (this.fieldBinding != null) {
      throw new AssertionError();
    }
    this.fieldBinding = fieldBinding;
  }

看到这里就把findAndParseTargets()方法分析完了。大体总结一下就是把一些变量、参数等初始化到了BindingClass类中。
也就是说上面process()方法中的第一步已经分析完了,下面我们来继续看第二部分.

第二步:bindingClass.brewJava()

继续查看BindingClass.brewJava()方法的实现:

Collection<JavaFile> brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(binderClassName)
        .addModifiers(PUBLIC, FINAL)
        .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetTypeName));

    result.addMethod(createBindMethod(targetTypeName));

    List<JavaFile> files = new ArrayList<>();
    if (isGeneratingUnbinder()) {
      // 生成$$_ViewBinding类
      files.add(JavaFile.builder(unbinderClassName.packageName(), createUnbinderClass())
          .addFileComment("Generated code from Butter Knife. Do not modify!")
          .build()
      );
    } else if (!isFinal) {
      result.addMethod(createBindToTargetMethod());
    }
    // 生成$$_ViewBinder类
    files.add(JavaFile.builder(binderClassName.packageName(), result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build());

    return files;
  }

看到这里感觉不用再继续分析了,该方法就是使用javaopet来生成对应$$_ViewBinder.java类。

到这里我们已经知道在编译的过程中会去生成一个对应的$$_ViewBinder.java文件,该类实现了ViewBinder接口。它内部会去生成对应findViewByid()以及setOnClickListener()等方法的代码,它生成了该类后如何去调用呢?我们也没有发现new $$_ViewBinder的方法。不要忘了上面我们看到的ButterKnife.bind(this);。接下来我们看一下ButterKnife.bind(this);方法的实现:

/**
   * BindView annotated fields and methods in the specified {@link Activity}. The current content
   * view is used as the view root.
   *
   * @param target Target activity for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
  }

  /**
   * BindView annotated fields and methods in the specified {@link View}. The view and its children
   * are used as the view root.
   *
   * @param target Target view for view binding.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull View target) {
    return getViewBinder(target).bind(Finder.VIEW, target, target);
  }

调用了getViewBinder()的bind()方法,继续看getViewBinder()方法:

static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<>();
...

@NonNull @CheckResult @UiThread
  static ViewBinder<Object> getViewBinder(@NonNull Object target) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
    return findViewBinderForClass(targetClass);
  }

  @NonNull @CheckResult @UiThread
  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) {
     // BINDERS是一个Map集合。也就是说它内部使用Map缓存了一下,先去内存中取
     ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder;
    }
    // 内存中没有缓存该类
    String clsName = cls.getName();
    // 通过类名判断下是不是系统的组件
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      // 通过反射获取到对应通过编译时注解生成的$_ViewBinder类的实例
      Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder");
      //noinspection unchecked
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
      if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to create view binder for " + clsName, e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to create view binder for " + clsName, e);
    }
    // 通过反射来操作毕竟会影响性能,所以这里通过Map缓存的方式来进行优化
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }

到这里就彻底分析完了ButterKnife.bind(this);的实现,它其实就相当于new了一个$_ViewBinder类的实例。当然这样用起来是非常方便的,毕竟我们手动的去new类多不合理,虽然他里面用到了反射会影响一点点性能,但是他通过内存缓存的方式优化了,我感觉这种方式是利大于弊的。

那$_ViewBinder类里面都是什么内容呢? 我们去看一下该类的代码,但是它生成的代码在哪里呢?
image

开始看一下SimpleActivity_ViewBinder.bind()方法:

public final class SimpleActivity_ViewBinder implements ViewBinder<SimpleActivity> {
  @Override
  public Unbinder bind(Finder finder, SimpleActivity target, Object source) {
    return new SimpleActivity_ViewBinding<>(target, finder, source);
  }
}

接着看SimpleActivity_ViewBinding类:

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

  private View view2130968578;

  private View view2130968579;

  public SimpleActivity_ViewBinding(final T target, Finder finder, Object source) {
    this.target = target;

    View view;
    target.title = finder.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
    target.subtitle = finder.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
    view = finder.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = finder.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 = finder.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = finder.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 = finder.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
    target.headerViews = Utils.listOf(
        finder.findRequiredView(source, R.id.title, "field 'headerViews'"), 
        finder.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), 
        finder.findRequiredView(source, R.id.hello, "field 'headerViews'"));
  }

  @Override
  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(null);
    view2130968578.setOnLongClickListener(null);
    view2130968578 = null;
    ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
    view2130968579 = null;

    this.target = null;
  }
}

可以看到他内部会通过findViewByid()等来找到对应的View,然后将其赋值给target.xxxx,所以这样就相当于把所有的控件以及事件都给初始化了,以后就可以直接使用了,通过这里也可以看到我们在使用注解的时候不要把控件或者方法声明为private的。

总结一下:

  • ButterKnifeProcessor会生成$$_ViewBinder类并实现了ViewBinder接口。
  • $$_ViewBinder类中包含了所有对应的代码,会通过注解去解析到id等,然后通过findViewById()等方法找到对应的控件,并且复制给调用该方法的来中的变量。这样就等同于我们直接
    使用View v = findViewByid(R.id.xx)来进行初始化控件。
  • 上面虽然生成了$$_ViewBinder类,但是如何去调用呢? 就是在调用ButterKnife.bind(this)时执行,该方法会通过反射去实例化对应的$$_ViewBinder类,并且调用该类的bind()方法。

  • Butterknife除了在Butterknife.bind()方法中使用反射之外,其他注解的处理都是通过编译时注解使用,所以不会影响效率。

  • 使用Butterknife是不要将变量声明为private类型,因为$$_ViewBinder类中会去直接调用变量赋值,如果声明为private将无法赋值。
    java
    @BindView(R2.id.title) TextView title;

更多文章请见AndroidNote,欢迎start&fork&submit

蓝牙学习笔记之SMP协议(十四) 目录 写在前面 SM规范简述 安全管理器 加密工具 配对流程 安全属性 IO功能 加密密钥大小 配对算法 低功耗蓝牙安全 密钥和值的定义 分发密钥的生成 密钥分配 安全管理器会话协议 介绍 ​​​​​​​命令格式 写在前面 安全规范协议,主要是描述LE控制器设备配对、认证、加密等过程进行规定,同时也对相关算法进行了说明。 低功耗蓝牙的安全管理经历了几个阶段,L... 阅读详情

相关推荐

别再拿空气标定法糊弄了!手把手教你搞定水下相机标定(附Python代码)

本文详细解析水下相机标定的关键技术,揭示传统空气标定法失效原因,并提供基于单视点模型(SVP)的完整Python解决方案。通过折射效应分析、标定板设计优化和代码实战演示,帮助读者掌握水下标定核心方法,有效提升水下视觉系统测量精度。

weixin_30530939的博客 210

Android Studio使用butterknife出现Cause: superclass access check failed: class butterknife.compiler报错

然后打开gradle-wrapper.properties替换新的gradle包,以下版本本人亲测可用。解决方法如下:打开setting找到Gradle把JDK版本修改为java 15。接着在build.gradle也要修改对应的gradle版本,不然会出现新的报错。新版Android studio使用butterknife出现报错如下。

Dragon_hopex的博客 9065

【目标检测】yoloV5算法详解

一、与yoloV4相比,yoloV5的改进 输入端:在模型训练阶段,使用了Mosaic数据增强、自适应锚框计算、自适应图片缩放 基准网络:使用了FOCUS结构和CSP结构 Neck网络:在Backbone和最后的Head输出层之间插入FPN_PAN结构 Head输出层:训练时的损失函数GIOU_Loss,预测筛选框的DIOU_nns 二、yoloV5结构框架 CBL:CBL模块是由Conv+BN+Leaky_relu激活函数组成 Res unit:借鉴ResNet中的残差结构,用来构建深层网络,CBM是

qq_40373651的博客 2万+

superclass access check failed: class butterknife.compiler.ButterKnifeProcessor$RScanner

原来有17和1.8版本,这里需要用到11版本,下载并应用11版本即可,如果没有生效,clean并rebuild一下。解决办法:修改配套gradle版本,修改java版本。原因:gradle版本不适配,java版本不适配。

fynzhy的博客 5508

深入解析.Net中SM2与sm-crypto的跨平台加解密实践

本文深入探讨了在.Net平台与前端sm-crypto库之间实现SM2国密算法跨平台加解密的实践方案。针对对接中常见的密钥格式、密文拼接顺序(C1C3C2与C1C2C3)等核心难点,提供了基于BouncyCastle的完整工具类实现、前后端联调示例及问题排查指南,旨在帮助开发者构建安全、兼容的数据加密通道。

weixin_29322855的博客 370

康耐视CogPMAlignTool模板匹配详解

康耐视CogPMAlignTool是一款基于边缘特征的模板匹配工具,适用于工业视觉检测。该工具提供PatMax、PatQuick、PatFlex等不同算法,分别对应高精度、快速匹配和柔性表面等应用场景。关键参数包括特征粒度(粗糙/精细)、边缘阈值、弹性范围等,支持旋转缩放匹配和极性忽略功能。工具操作流程包括图像采集、参数设置、模板训练和运行检测,通过调整查找概数、接收阈值等参数可优化识别效果。配套的卡尺工具(CogCaliperTool)通过一维投影和边缘筛选实现精准定位,适用于高精度尺寸测量场景。

2502_92473474的博客 1318

Android Studio 更新至2022.2.1,运行项目失败

Cause: superclass access check failed: class butterknife.compiler.ButterKnifeProcessor$RScanner (in unnamed module @0x69d73d) cannot access class com.sun.tools.javac.tree.TreeScanner (in module jdk.compiler) because module jdk.compiler does not export com.

qq_54087555的博客 7206

神经网络基础概念总结(小白入门自用)

神经网络人工神经网络多层感知机激活函数反向传播算法损失函数权值初始化正则化方法 人工神经网络 人工神经元:人工神经元是人工神经网络的基本单元。模拟生物神经元,人工神经元有1个或者多个输入(模拟多个树突或者多个神经元向该神经元传递神经冲动);对输入进行加权求和(模拟细胞体将神经信号进行积累和树突强度不同);对输入之和使用激活函数计算活性值(模拟细胞体产生兴奋或者抑制);输出活性值并传递到下一个人工神经元(模拟生物神经元通过轴突将神经冲动输入到下一个神经元)。 人工神经网络:大量的人工神经元以某种链接方式得来的

qq_42011158的博客 650

Android解决报错 superclass access check failed: class

以上就是今天的内容,由于gradle版本和Java不匹配导致的项目运行失败,最后经过修改gradle和Java版本后成功运行起来,就是一个小问题,需要排查一下即可解决!!

u012556114的博客 9078

SpringBoot 整合 Drools:从环境搭建到复杂业务落地,精通规则引擎实战指南

本文介绍了Drools规则引擎的基础概念及其与SpringBoot的整合方法。首先概述了规则引擎的核心价值,包括业务规则解耦、可维护性提升等。重点讲解了Drools的核心组件(规则、事实、会话等)及其工作原理。随后详细说明了在SpringBoot项目中集成Drools的步骤,包括添加依赖、配置自动加载规则文件、创建KieContainer等关键配置类。文章还提供了完整的DRL规则文件示例和SpringBoot集成配置代码,为开发人员提供了从理论到实践的完整指导。通过这种方式,可以轻松实现业务规则与系统代码的

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 1435

CDD数据库文件制作(三)——服务配置(0x10)

根据诊断调查表进行每一步配置的详细操作,与背后逻辑解释 1.会话切换配置 1.1.根据诊断调查表进行信息提取(0x1001) 1.2.会话转换配置(0x1001) 1.3.根据诊断调查表进行信息提取(0x1002) 1.4.会话转换配置(0x1002) 1.5.根据诊断调查表进行信息提取(0x1003) 1.6.会话转换配置(0x1003) 1.7.会话切换配置完成后效果图 2.安全等级配置信息提取 2.1.安全访问配置(0x1001) 2.2.安全访问配置(0x1002) 2.3.安全访问配置(0x100

LOVE135149的博客 5841

Cause: superclass access check failed: class butterknife.compiler.ButterKnifeProcessor$RScanner

gradle 版本是7.3.3。

shi450561200的博客 2902

【ATC/DTC方法第一期】经典平均/昼夜温度周期ATC/DTC方法介绍(含Python代码实现)

【ATC/DTC方法第一期】经典平均/昼夜温度周期ATC/DTC方法介绍(含Python代码实现)

WW、forever的博客 486

编译时注解Butterknife源码详解之初入篇(雷惊风)

今天是阳历3月8号,在此祝所有女士们节日快乐啊,本来我是不知道今天是节日的,早晨上班,看见领导在群里发红包,这年头,谁还看红包上写的啥,年会开挂抢红包,都没抢多少,二话没说就抢了,后来才看到红包上的内容,祝所有女生节日快乐,男生自觉点...,我也是醉了,文字太长,没看见,最后又加了点钱发回去了。真尴尬,高潮是,我抢了发回去了吧,还有男生抢,都跟我一样啊,看不见嘛。唉,这年头,也是,没办法,看见红包

刘永雷的专栏 1067

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

ButterKnife源码分析

使用Butter Knife己经很长时间了,一直很喜欢用。因为它能帮我节省很多重复性的绑定控件代码。今天就来讲讲它的源码吧。只是总结我对它源码的一个理解。先看下它的使用效果:public TestActivity extends Activity{ @Bind(R.id.top_pic) ImageView topPic; @Bind(R.id.top) TextView

u014486880的博客 3045

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

O3-开源框架使用之Butterknife 8.8.1及源码浅析

零、前言 我最喜欢的框架,没有之一: 编译期生成代码的方式,对运行时没有任何副作用。 加上AndroidStudio快捷键,简直好用之至。 添加依赖: implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compile...

张风捷特烈的博客 331

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Android开发MVVM设计模式的实现及ButterKnife兼容结合源码详解并总结优缺点

MVVM设计模式主要是view通过中间件viewModel与model数据模型交互, 采用谷歌自带DataBinding 组件只需要在app.gradle开启就行: dataBinding { enabled = true } View布局文件xml: <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" x

SunshineBoy的博客 1956

ButterKnife 源码解析 (二)-- Java 文件结构化文本详解

文章目录1. APT (Annotation Processing Tool)2. 结构体语言2.1 PackageElement2.2 ExecuteableElement2.3 TypeElement2.4 VariableElement2.5 需要掌握的 API 1. APT (Annotation Processing Tool) 是一种处理注释的工具,它对源代码文件进行检测并...

怪咖先森的博客 428

注解(Annotation)自定义注解(四)--编译期注解解析讲解和手写ButterKnife

注解(Annotation)自定义注解(四)–编译期注解解析讲解和手写ButterKnife前言前面两篇讲解了运行期注解的使用和xutils源码的解析,以及手动打造自己的IOC框架。但是运行期注解由于性能问题被一些人所诟病,所以这里我们讲下编译器注解的使用和实战。介绍编译器注解的核心原理依赖APT(Annotation Processing Tolls)实现,例如,我们常用的ButterKnife,

a847427920的博客 1096

mvvm模式 android 缺点,Android开发MVVM设计模式的实现及ButterKnife兼容结合源码详解并总结优缺点...

MVVM设计模式主要是view通过中间件viewModel与model数据模型交互,咱们可以看一下流程图: 本篇采用谷歌自带DataBinding 组件实现MVVM,只需要在app.gradle开启就行:dataBinding {enabled = true}View布局文件xml:xmlns:tools="http://schemas.android.com/tools">name="vi...

weixin_30064143的博客 196

Butterknife源码分析

剖析源码,内容充实,请准备好你的流量

郭霖 573

【Android源码解析】一篇搞定“路由、网络层、UI层、通信层

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!资料⬅专栏获取。

asdzxc324as的博客 1014

ButterKnife源码剖析

转载请注明:http://blog.youkuaiyun.com/chenkai19920410/article/details/51020151      ButterKnife是Jake Wharton大神写开源框架。项目托管地址:   https://github.com/JakeWharton/butterknife。   相信不少人已经非常熟悉他的使用了。网上有很多介绍其使用的文章。本文主要是

chenkai19920410的专栏 7712

ButterKnife(黄油刀)基本使用与源码解析

前言 ButterKnife又称黄油刀,是一款快速绑定Android视图中字段和方法的注解框架,也是Android开发中比较常用的一款快速注解框架,通过ButterKnife的合理运用,我们可以避免重复地书写findViewById,在各种场合下快速地绑定view中的多种事件,大大提高开发的效率,它具有如下几点主要的功能: 通过@BindView注解消除findViewById的使用 将多个v...

ai-exception的博客 5172

ButterKnife源码完全解析

本文主要介绍ButterKnife基本使用,并对butterKnife如何实现模板代码生成和依赖注入做了详细分析

S.H.的部落格林 589
上一篇: 注解使用
下一篇: volley-retrofit-okhttp之我们该如何选择网路框架
CharonChui
博客等级 码龄12年 66粉丝 134原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值