Android技术分享——APT实现ButterKnife【实战学习】

news2024/9/25 9:36:58

APT

APT (Annotation Processing Tool)

是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。

简单说:根据规则,帮我们生成代码、生成类文件

编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife、Dagger、ARouter 等 ButterKnife 原理分析

使用 ButterKnife :

① 添加依赖 :

dependencies {
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}

② Activity 中使用 ButterKnife :

package kim.hsl.apt;
​
import androidx.appcompat.app.AppCompatActivity;
​
import android.os.Bundle;
import android.widget.TextView;
​
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity {
​
    @BindView(R.id.hello)
    TextView hello;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        ButterKnife.bind(this);
    
        hello.setText("ButterKnife");
    }
}

BindView 注解分析 : 在 TextView hello 成员变量处添加了 @BindView(R.id.hello) 注解 ;

@Target(FIELD) 元注解 : 表示其作用与类的成员字段 ;

@Retention(RUNTIME) 元注解 : 表示该注解保留到运行时阶段 ;

int value() 注解属性 : 只有一个注解属性 , 并且属性名是 value , 则使用注解时 “value =” 可省略 ;

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

TextView hello 需要使用 findViewById 进行赋值 , 在上述代码中没有写 findViewById 相关的代码 ; 肯定是在某个地方执行了 findViewById 的方法 ;

ButterKnife.bind(this) 代码就是执行了 findViewById 方法 ;

ButterKnife 用到了编译时技术会 , 在项目编译时 , 会生成 MainActivity_ViewBinding 类 , 在该类中 , 会查找添加了 @BindView 直接的成员变量 , 再获取 注解属性 value 的值 , 然后调用 findViewById 方法获取组件并为成员变量赋值 ;

// Generated code from Butter Knife. Do not modify!
package kim.hsl.apt;
​
import android.view.View;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
​
public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;
​
  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }
​
  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;
​
    target.hello = Utils.findRequiredViewAsType(source, R.id.hello, "field 'hello'", TextView.class);
  }
​
  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
​
    target.hello = null;
  }
}

ButterKnife 涉及到的源码 :

public final class ButterKnife {
  /**
   * 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) {
​
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }
​
  /**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link View} as the view root.
      *
   * @param target Target class for view binding.
   * @param source View root on which IDs will be looked up.
      */
    @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);
    }
  }
​
  @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;
  }
}

实战APT实现Butterknife

在开始之前,我们先做个假设,要是我们可以把以下代码摘出来到另一个文件,通过某种方式自动生成这种代码会发生什么事。

package com.calm.baseknowledge02;
​
import com.calm.annotations.IBind;
​
import android.view.View;
​
public class MainActivity_ViewBinding implements IBind<com.calm.baseknowledge02.MainActivity> {
    @Override
    public void bind(com.calm.baseknowledge02.MainActivity target) {
        target.tvInfo = (android.widget.TextView) target.findViewById(2131231077);
        target.btnChange = (android.widget.Button) target.findViewById(2131230807);
        target.findViewById(2131230807).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                target.onClick(view);
            }
        });
        target.findViewById(2131230807).setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                target.onLongClick(view);
                return true;
            }
        });
    }
}
​
​

我们将findViewById、setOnClickListener、setOnLongClickListener这种代码摘到另一个文件,然后实例化该类与原始的Activity进行绑定。由于这些代码非常类似,我们可以通过模板化处理,就不需要我们在一行行去写了,只需要一个注解就搞定。那么关键来了,这份代码如何生成,这时候我们的APT就该上场了。 先将我们的注解写出来,由于这次的注解只需要在源码期存在即可,因此我们的作用域有一定变化,且较上篇会简单些。

/**
 * 绑定控件
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}
/**
 * 单击事件
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface OnClick {
    int[] value() default -1;
}

/**

  • 长按事件 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface OnLongClick {
    int[] value() default -1;
}

为了做绑定和注入,我们统一一个公共的接口。

public interface IBind<T> {
    void bind(T target);
}

注解处理器需要用到的相关插件,已经有现成的了就不需要我们从头去弄了。

dependencies {
    implementation project(path: ':annotations')
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
}

关键的类就是注解处理器的类,请看代码

/**
 * 注解处理器
 */
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor {
    private Filer filer;
    private Messager messager;
    private static String END_WITH = "_ViewBinding";
    private static String BIND_VIEW = "BindView";
    private static String ON_CLICK = "OnClick";
    private static String ON_LONG_CLICK = "OnLongClick";
​
    /**
     * 初始化
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }
​
    /**
     * 支持的版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
​
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        types.add(OnLongClick.class.getCanonicalName());
        return types;
    }
​
    /**
     * 真正做事的都在这个方法里
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if(set.size() == 0){
            return false;
        }
        //getElementsAnnotatedWith 搜索对应注解的元素
        //ExecutableElement 注解在方法节点
        //VariableElement 注解在成员变量节点
        //TypeElement 注解在类节点
        //PackageElement 注解在包节点
        Set<? extends Element> bindViewElement = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Set<? extends Element> onClickElement = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        Set<? extends Element> onLongClickElement = roundEnvironment.getElementsAnnotatedWith(OnLongClick.class);
        //存储节点的容器 结构为 <com.calm.baseknowledge02.MainActivity,<BindView,List<>>>
        Map<String,Map<String, List<Element>>> map = new HashMap<>();
        //将BindView注解的成员变量节点进行分类存储
        for (Element element : bindViewElement) {
            //得到成员变量节点
            VariableElement variableElement = (VariableElement) element;
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();
            String canonicalName = packageName+"."+activityName;
            Map<String, List<Element>> annotationMap = map.get(canonicalName);
            if(annotationMap == null){
                annotationMap = new HashMap<>();
                map.put(canonicalName,annotationMap);
                List<Element> bindViewAnnotations = new ArrayList<>();
                annotationMap.put(BIND_VIEW,bindViewAnnotations);
                bindViewAnnotations.add(element);
            }else {
                List<Element> bindViewAnnotations = annotationMap.get(BIND_VIEW);
                if(bindViewAnnotations == null){
                    bindViewAnnotations = new ArrayList<>();
                    annotationMap.put(BIND_VIEW,bindViewAnnotations);
                }
                bindViewAnnotations.add(element);
            }
        }
        //将OnClick注解节点分类存储
        for (Element element : onClickElement) {
            //得到方法节点
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
            TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
            String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();
            String canonicalName = packageName+"."+activityName;
            Map<String, List<Element>> annotationMap = map.get(canonicalName);
            if(annotationMap == null){
                annotationMap = new HashMap<>();
                map.put(canonicalName,annotationMap);
                List<Element> onClickAnnotations = new ArrayList<>();
                annotationMap.put(ON_CLICK,onClickAnnotations);
                onClickAnnotations.add(element);
            }else {
                List<Element> onClickAnnotations = annotationMap.get(ON_CLICK);
                if(onClickAnnotations == null){
                    onClickAnnotations = new ArrayList<>();
                    annotationMap.put(ON_CLICK,onClickAnnotations);
                }
                onClickAnnotations.add(element);
            }
        }
        //将OnLongClick注解节点分类存储
        for (Element element : onLongClickElement) {
            //得到方法节点
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
            TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
            String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();
            String canonicalName = packageName+"."+activityName;
            Map<String, List<Element>> annotationMap = map.get(canonicalName);
            if(annotationMap == null){
                annotationMap = new HashMap<>();
                map.put(canonicalName,annotationMap);
                List<Element> onLongClickAnnotations = new ArrayList<>();
                annotationMap.put(ON_LONG_CLICK,onLongClickAnnotations);
                onLongClickAnnotations.add(element);
            }else {
                List<Element> onLongClickAnnotations = annotationMap.get(ON_LONG_CLICK);
                if(onLongClickAnnotations == null){
                    onLongClickAnnotations = new ArrayList<>();
                    annotationMap.put(ON_LONG_CLICK,onLongClickAnnotations);
                }
                onLongClickAnnotations.add(element);
            }
        }
        //分类完成,开始写模板代码了
        if(map.size() == 0){
            return false;
        }
        Writer writer = null;
        Iterator<String> canonicalNames = map.keySet().iterator();
        while (canonicalNames.hasNext()){
            //全类名
            String canonicalName = canonicalNames.next();
            print(canonicalName);
            //包名
            String packageName = canonicalName.substring(0,canonicalName.lastIndexOf("."));
            print(packageName);
            String activityName = canonicalName.substring(canonicalName.lastIndexOf(".")+1);
            print(activityName);
            try {
//package com.calm.baseknowledge02;
//import com.calm.annotations.IBind;
//public class MainActivity_ViewBinding implements IBind<MainActivity> {
//    @Override
//    public void bind(MainActivity target) {
//        target.tvInfo = target.findViewById(R.id.tvInfo);
//        target.btnChange = target.findViewById(R.id.btnChange);
//    }
//}
                //创建.java文件,名称类似com.calm.baseknowledge02.MainActivity_ViewBinding
                JavaFileObject sourceFile = filer.createSourceFile(canonicalName+END_WITH);
                writer = sourceFile.openWriter();
                writer.write("package "+packageName+";\n");
                writer.write("import com.calm.annotations.IBind;\n");
                writer.write("import android.view.View;\n");
                writer.write("public class "+activityName+END_WITH+" implements IBind<"+canonicalName+"> {\n");
                writer.write("@Override\n");
                writer.write("public void bind("+canonicalName+" target){\n");
                //注解map
                Map<String,List<Element>> annotationMap = map.get(canonicalName);
                if(annotationMap != null){
                    //处理BindView注解
                    List<Element> bindViews = annotationMap.get(BIND_VIEW);
                    if(bindViews != null && bindViews.size() > 0){
                        for (Element bindView : bindViews) {
                            int id = bindView.getAnnotation(BindView.class).value();
                            writer.write("target."+bindView.getSimpleName()+" = ("+bindView.asType()+")target.findViewById("+id+");\n");
                        }
                    }
                    //处理OnClick注解
                    List<Element> onClicks = annotationMap.get(ON_CLICK);
                    if(onClicks != null && onClicks.size() > 0){
                        for (Element onClick : onClicks) {
                            int[] ids = onClick.getAnnotation(OnClick.class).value();
                            for (int id : ids) {
//        findViewById(R.id.btnChange).setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View view) {
//
//            }
//        });
                                writer.write("target.findViewById("+id+").setOnClickListener(new View.OnClickListener() {\n");
                                writer.write("@Override\n");
                                writer.write("public void onClick(View view) {\n");
                                writer.write("target."+onClick.getSimpleName().toString()+"(view);\n");
                                writer.write("}\n");
                                writer.write("});\n");
                            }
                        }
                    }
                    //处理OnLongClick注解
                    List<Element> onLongClicks = annotationMap.get(ON_LONG_CLICK);
                    if(onLongClicks != null && onLongClicks.size() > 0){
                        for (Element onLongClick : onLongClicks) {
                            int[] ids = onLongClick.getAnnotation(OnLongClick.class).value();
                            for (int id : ids) {
        //        findViewById(R.id.btnChange).setOnLongClickListener(new View.OnLongClickListener() {
//            @Override
//            public boolean onLongClick(View view) {
//
//                return false;
//            }
//        });
                                writer.write("target.findViewById("+id+").setOnLongClickListener(new View.OnLongClickListener() {\n");
                                writer.write("@Override\n");
                                writer.write("public boolean onLongClick(View view) {\n");
                                writer.write("target."+onLongClick.getSimpleName().toString()+"(view);\n");
                                writer.write("return true;\n");
                                writer.write("}\n");
                                writer.write("});\n");
                            }
                        }
                    }
                }
                writer.write("}\n");
                writer.write("}\n");
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(writer != null){
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }
    private void print(String msg){
        messager.printMessage(Diagnostic.Kind.NOTE,msg);
    }
}

关键的地方都有注释,就不一一解释了。这个类都不需要我们主动调用,在编译的时候由javac帮我们进行调用。然后编译下代码,我们就会生成一个我们最开始的那样一个类。 为了能使用这些类,我们还要写一个注入的类

public class CButterknife{
    public static void bind(Object o) {
        String name = o.getClass().getName()+"_ViewBinding";
        try {
            Class<?> clzz = Class.forName(name);
            IBind iBind = (IBind) clzz.newInstance();
            iBind.bind(o);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}
​

这个类也很简单,由于我们生成的类是有命名套路的。比如类为MainActivity,那么生成的类则为MainActivity_ViewBinding,那么我们要进行注入就很简单了,通过反射实例化该类,调用bind方法即可,就这么简单。然后在Activity中使用就更简单,只需要一句代码即可。

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tvInfo)
    TextView tvInfo;
    @BindView(R.id.btnChange)
    Button btnChange;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CButterknife.bind(this);
    }
    @OnClick(R.id.btnChange)
    public void onClick(View view){
        if(view.getId() == R.id.btnChange){
            tvInfo.setText("我是点击按钮后变化的数据");
        }
    }
    @OnLongClick(R.id.btnChange)
    public void onLongClick(View view){
        if(view.getId() == R.id.btnChange){
            tvInfo.setText("我是长按按钮后变化的数据");
        }
    }
}

这里需要注意的是CButterknife.bind(this)这句代码一定要在setContentView之后调用。

以上是Android开发技术中APT实现ButterKnife的一些原理解析和实战演练,更多Android技术分享尽在《Android核心技术手册》里面几十个技术板块划分,上千个小技术点带你进阶进入高工领域。

文末

编译时技术 最重要的作用就是在编译时可以 生成模板代码 ;由于生成代码操作是在编译时进行的 , 不会对运行时的性能产生影响 。

程序的周期 :

  • 源码期 : 开发时 , 刚编写完 " .java " 代码 , 还未编译之前 , 就处于源码期 ;
  • 编译期 : 程序由 java 源码编译成 class 字节码文件 ;
  • 运行期 : 将字节码文件加载到 Java 虚拟机中运行 ;

编译时技术 APT 作用于 编译期 , 在这个过程中使用该技术 , 生成代码 ;编译时技术 2 2 2 大核心要素 : 在编译时 , 执行生成代码的逻辑 , 涉及到两个重要概念 。

① 编译时注解

② 注解处理器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/127543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python实现FA萤火虫优化算法优化支持向量机分类模型(SVC算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , 作…

maven第一篇:安装maven以及配置

本篇就是聊如何在电脑上安装maven&#xff0c;以及简单的配置基础环境。 首先需要了解什么适合maven&#xff0c;对于这个理论知识&#xff0c;还是老规矩直接复制一下&#xff1b; Maven 是一款基于 Java 平台的项目管理和整合工具&#xff0c;它将项目的开发和管理过程抽象…

提速300%,PaddleSpeech语音识别高性能部署方案重磅来袭!

在人机交互的过程中&#xff0c;语音是重要的信息载体&#xff0c;而语音交互技术离不开语音识别与语音合成技术。飞桨语音模型库PaddleSpeech为开发者们使用这些技术提供了便捷的环境。本次PaddleSpeech迎来重大更新——1.3版本正式发布。让我们一起看看&#xff0c;这次Paddl…

这样实操一下 JVM 调优,面试超加分

1.写在前面 前段时间一位读者面了阿里&#xff0c;在二面中被问到 GC 日志分析&#xff0c;感觉回答的不是很好&#xff0c;过来找我复盘&#xff0c;大致听了他的回答&#xff0c;虽然回答出了部分&#xff0c;但是没抓到重点。 GC 日志分析算是 JVM 调优中比较难的部分&…

【XR】如何提高追踪保真度,确保内向外追踪系统性能

Constellation是Oculus研发的追踪系统。日前&#xff0c;负责AR/VR设备输入追踪的Facebook工程经理安德鲁梅利姆撰文介绍了他们是如何用基于Constellation追踪的控制器来提高交互保真度。具体整理如下&#xff1a; 我们的计算机视觉工程师团队一直在努力为Oculus Quest和Rift …

【再学Tensorflow2】TensorFlow2的模型训练组件(1)

TensorFlow2的模型训练组件&#xff08;1&#xff09;数据管道构建数据通道应用数据转换提升管道性能特征列特征列用法简介特征列使用示例激活函数常用激活函数激活函数使用示例Tensorflow模型中的层内置的层自定义模型中的层参考资料Tensorflow中与模型训练相关的组件主要包括…

图像采样与量化

数字图像有两个重要属性&#xff1a;空间位置(x,y)以及响应值I(x,y)。数字图像中像素的空间位置及响应值都是离散值&#xff0c;传感器输出连续电压信号。为了产生数字图像&#xff0c;需要把连续的数据转换为离散的数字化形式。采用的方式是图像量化与采样。 图像采样 图像量化…

【数据结构】(初阶):二叉搜索树

​ ✨前言✨ &#x1f393;作者&#xff1a;【 教主 】 &#x1f4dc;文章推荐&#xff1a; ☕博主水平有限&#xff0c;如有错误&#xff0c;恳请斧正。 &#x1f4cc;机会总是留给有准备的人&#xff0c;越努力&#xff0c;越幸运&#xff01; &#x1f4a6;导航助手&#x…

Docker+Selenium Grid运行UI自动化

简介 使用Selenium Grid可以分布式运行UI自动化测试&#xff0c;可以同时启动多个不同的浏览器&#xff0c;也可以同时启动同一个浏览器的多个session。这里使用Docker Compose来同时启动不同浏览器的容器和Selenium Grid&#xff0c;只需一条命令就把自动化运行环境部署好了。…

verilog仿真技巧与bug集合

文章目录赋值语句想法一些建议时钟信号关于异步fifo写入数据时wp1&#xff0c;读出数据时rp1一些自己的bug关于操作符&关于if-else关于modelsim使用1.初学者不建议在设计文件中加入仿真语句&#xff1b; 2.初学者也不会在tb里使用类似always一样的设计。 对于1.因为把仿真…

国产RISC-V处理器“黑马”跑分曝光!超过多数国内主流高性能处理器!

来源企业投稿 2010年&#xff0c;开源、开放、精简的RISC-V架构诞生。虽然距今仅有12年&#xff0c;但RISC-V迎来了众多玩家的积极参与&#xff0c;其技术、生态、应用都快速发展。在许多秉持匠心的技术人员的耕耘下&#xff0c;RISC-V也早已从传统强项物联网走出&#xff0c;…

error: ‘uint8_t’,‘uint16_t’ ,‘uint32_t’ does not name a type

文章目录1、报错error: ‘uint8_t’,‘uint16_t’ ,‘uint32_t’ does not name a type2、解决办法3、uint8_t此类数据类型补充4、码字不易&#xff0c;点点赞1、报错error: ‘uint8_t’,‘uint16_t’ ,‘uint32_t’ does not name a type 在网络编程PING程序时遇到的小bug&am…

【BUUCTF】MISC(第二页wp)

文章目录被劫持的神秘礼物刷新过的图片[BJDCTF2020]认真你就输了[BJDCTF2020]藏藏藏被偷走的文件snake[GXYCTF2019]佛系青年[BJDCTF2020]你猜我是个啥秘密文件菜刀666[BJDCTF2020]just_a_rar[BJDCTF2020]鸡你太美[BJDCTF2020]一叶障目[SWPU2019]神奇的二维码梅花香之苦寒来[BJD…

day02-Java基础语法

day02 - Java基础语法 1 类型转换 在Java中&#xff0c;一些数据类型之间是可以相互转换的。分为两种情况&#xff1a;自动类型转换和强制类型转换。 1.1 隐式转换(理解) ​ 把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量。这种转换方式是自动的&am…

外贸小白适合哪种邮箱?

除了一些企业指定的邮箱&#xff0c;大多数外贸人&#xff0c;尤其是小白的外贸人&#xff0c;都希望选择最合适的邮箱&#xff0c;赢在起跑线上。判断邮箱质量的两个主要因素是投递率和安全性。米贸搜的排列如下: 公共个人邮箱 此时常见的个人邮箱有国外的gmail、hotmail、雅…

2023 年软件开发人员可以学习的 10 个框架

开发者您好&#xff0c;我们现在处于 2023 年的第一周&#xff0c;你们中的许多人可能已经制定了 2023 年要学习什么的目标&#xff0c;但如果您还没有&#xff0c;那么您来对地方了。 早些时候&#xff0c;我分享了成为Java 开发人员、DevOps 工程师、React 开发人员和Web 开…

联合分析案全流程分析

联合分析(conjoint analysis)是一种研究消费者产品选择偏好情况的多元统计分析方法。比如消费者对于手机产品的偏好&#xff0c;对于电脑产品的偏好&#xff0c;也或者消费者对于汽车产品的偏好情况等。联合分析中涉及几个专业术语名词&#xff0c;分别如下所述&#xff1a; 联…

基于深度学习下的稳定学习究竟是什么?因果学习?迁移学习?之一

机器学习 | 稳定学习 | DGBR 深度学习 | 迁移学习 | 因果学习 众所周知&#xff0c;深度学习研究是机器学习领域中一个重要研究方向&#xff0c;主要采用数据分析、数据挖掘、高性能计算等技术&#xff0c;其对服务器的要求极其严格&#xff0c;传统的风冷散热方式已经不足以满…

C++---智能指针

目录 1. 为什么需要智能指针&#xff1f; 2. 内存泄漏 2.1 什么是内存泄漏&#xff0c;内存泄漏的危害 2.2 内存泄漏分类 2.4如何避免内存泄漏 3.智能指针的使用及原理 3.1 RAII 3.2 智能指针的原理 3.3 std::auto_ptr 3.4 std::unique_ptr 3.5 std::shared_ptr 3.6…

使用RMAN传输数据_创建可传输的表空间集(Creating Transportable Tablespace Sets)

传输数据比对相同的数据执行export/import或unload/load操作更快。因为对于用户定义的表空间&#xff0c;数据文件包含所有拷贝到目标位置的实际数据&#xff0c;你使用Data Pump只传输数据库对象的元数据到新数据库。 本章阐述如何使用RMAN通过还原备份来创建可传输的表空间集…