Android 模拟ButterKnife BindView注解实现绑定view(KAPT使用教程)

2023-05-16

简单回顾一下ButterKnife使用BindView注解初始化View过程:

  • 使用注解@BindView(R.id.main_title_view),并将要绑定的id传入
  • 调用BufferKnife.bind(this)方法开始绑定

其实最核心的是ButterKnife使用了APT技术,也就是Annotation Processing Tool的简称,翻译成中文就是"注解处理工具":

  • 它的作用是在编译阶段处理注解,生成相应的java文件,然后在运行阶段,通过反射调用那些生成的java类中的方法来进行绑定等操作

  • 生成的Java文件内容很简单,就是把当前Activity或者View传入进来,然后findViewById找到对应View并给带有注释的那些View赋值,所以使用BindView注解的变量都要声明为public

    public final class BindProxy$MainActivity {
      public BindProxy$MainActivity(MainActivity mainactivity) {
        mainactivity.mTitleView = (android.widget.TextView) mainactivity.findViewById(2131165289);
      }
    }
    
    
    • 说简单一点就是以前在Activity的那些findViewBy代码,ButterKnife帮我们通过注解配置的方式,自动转移到生成的这些类里面了
    • 所以BufferKnife.bind(this)的作用就是调用那些自动生成的代码

其实我们也可以省去APT生成代码那一步,直接代码运行时解析那些注解,然后通过反射赋值,也不需要固定带注解的变量都是public的,比如下面这样:

/**
 * 使用注解代替FindViewById()
 */
private void autoFindViews() {
	List<Field> fields = ClassUtil.getAllField(getClass());
        for (Field field : fields) {
            if (field.isAnnotationPresent(BindView.class)) {
                BindView bindView = field.getAnnotation(BindView.class);
                int viewId = bindView.value();
                if (viewId > 0) {
                    //反射访问私有变量需要设置这个
                    field.setAccessible(true);
                    try {
                        View view = findViewById(viewId);
                        if (view != null) {
                            if (field.isAnnotationPresent(BindClick.class)) {
                                view.setOnClickListener(this);
                            }
                            field.set(this, view);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }
            }
}

那么为什么还有那么多人用BufferKnife,而不是自己用几行代码反射搞定呢?
这主要还是取舍问题,通过上面我们知道BufferKnife也不是特别完美的:

  • 它需要把原来私有变量都变成public,违反了"地米特法则"
  • 增加了APT生成类的过程,这意味着项目编译时间变长了

但是如果简单一点使用反射,则会消耗更多的系统资源,比较适合项目还比较小,使用反射对整个项目性能影响不大的情况,而BufferKnife则适合较大项目,在编译时处理好,运行时减少使用反射节提高性能

下面我们使用简单的项目来模拟BufferKnife解析注解绑定View过程

1、创建名叫"annotation"的Java Library类型Module:主要用于存放自定义的注解以及一些常量

  • build.gradle:

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    }
    
    sourceCompatibility = "7"
    targetCompatibility = "7"
    
    
  • 创建一个注解类型的java类:BindView.java

    • ElementType.FIELD表示该注解只能用在变量上
    • RetentionPolicy.CLASS表示该注解用于编译时解析
    /**
     * 通过配置控件id,替换findViewById
     *
     * @author zhujie
     * @date 2019-09-13
     * @time 08:32
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
        int value();
    }
    
  • 创建一个常量类:Constant.java

    /**
     * 相关常量定义
     *
     * @author zhujie
     * @date 2019-09-14
     * @time 06:23
     */
    public class Constant {
        /**
         * 生成类所在包名
         */
        public static final String GENERATE_PACKAGE = "com.agilezhu.annotationkit.generate";
        /**
         * 生成类名固定前缀
         */
        public static final String GENERATE_CLASS_NAME_HEAD = "BindProxy$";
        /**
         * 生成类完整类名开头:包名+类名固定前缀
         */
        public static final String GENERATE_CLASS_FULL_NAME_HEAD = GENERATE_PACKAGE + "." + GENERATE_CLASS_NAME_HEAD;
    }
    
    

2、创建名叫"compiler"的Java Library类型Module:主要用于存放解析注解的处理器

  • build.gradle:

    apply plugin: 'java-library'
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation project(':annotation')
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'//auto-service本身也是个注解处理器
        implementation 'com.google.auto.service:auto-service:1.0-rc4'//注解 processor 类,并对其生成 META-INF 的配置信息
        implementation 'com.squareup:javapoet:1.8.0' //通过类调用的形式来生成java代码,避免手动拼接字符串
    
    }
    
    sourceCompatibility = "7"
    targetCompatibility = "7"
    
    
    • auto-service:用于自动注册配置注解处理器processor 类,使用@AutoService(Processor.class)来标记注解处理器类;需要注意的是,不仅要implementation这个auto-service,还要annotationProcessor,因为auto-service里既包含AutoService注解,也包含处理该注解的解释器,少了其中一个都无法正常生成代码
    • javapoet:这个主要用来通过调用方法来创建java类,相对于拼接字符串来说更加方便不容易出错
  • 接着创建自定义的注解处理器BindProcessor.java:这个类主要用于解析用户标注的注解,然后生成自定义java处理类

    /**
     * 处理绑定类型的注解
     *
     * @author zhujie
     * @date 2019-09-13
     * @time 08:29
     */
    @AutoService(Processor.class)
    public class BindProcessor extends AbstractProcessor {
        private Filer mFiler; //文件相关工具类:用于保存生成的java类文件
        private Elements mElementUtils; //元素相关工具类:用于获取java类文件
        private Messager mMessager;//用于打印日志
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mFiler = processingEnv.getFiler();
            mElementUtils = processingEnv.getElementUtils();
            mMessager = processingEnv.getMessager();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            //返回该注解处理器能够处理哪些注解
            Set<String> types = new LinkedHashSet<>();
            types.add(BindView.class.getName());
            return types;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            //返回当前注解处理器支持的java版本号
            return SourceVersion.latest();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //获取所有的源码文件
            Set<? extends Element> elements = roundEnvironment.getRootElements();
            for (Element element : elements) {
                if (!(element instanceof TypeElement)) {//判断是否class类
                    continue;
                }
                //转换成class类型
                TypeElement typeElement = (TypeElement) element;
                //当前文件的类名
                String classSimpleName = element.getSimpleName().toString();
                //将要生成的java完整类名:BindProxy$+当前类名
                String targetClassName = GENERATE_CLASS_NAME_HEAD + element.getSimpleName();
    
                //创建方法(构造方法)
                MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("<init>")
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(ClassName.get(typeElement.asType()), classSimpleName.toLowerCase());
    
                //获取当前类里所有元素
                List<? extends Element> members = mElementUtils.getAllMembers(typeElement);
                //当前类里所有添加了BindView注释的元素
                List<Element> annotationMembers = new ArrayList<>();
                for (Element member : members) {
                    BindView bindViewAnnotation = member.getAnnotation(BindView.class);
                    if (bindViewAnnotation != null) {
                        annotationMembers.add(member);
                        String paramName = classSimpleName.toLowerCase();
                        //构造方法中添加初始化代码:findViewById
                        bindMethodBuilder.addStatement(
                                String.format(
                                        paramName + ".%s = (%s) " + paramName + ".findViewById(%s)"
                                        , member.getSimpleName()
                                        , ClassName.get(member.asType()).toString()
                                        , bindViewAnnotation.value()));
                    }
                }
                //如果该类中没有我们自定义的注解,则不生成对应java处理类
                if (annotationMembers.isEmpty()) {
                    continue;
                }
    
                //创建类
                TypeSpec bindProxyClass = TypeSpec.classBuilder(targetClassName)
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                        .addMethod(bindMethodBuilder.build())
                        .build();
                //创建java文件
                JavaFile bindProxyFile = JavaFile
                        .builder(GENERATE_PACKAGE, bindProxyClass)
                        .build();
                try {
                    //保存java类文件
                    bindProxyFile.writeTo(mFiler);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    
    }
    
    • 在init方法中可以通过ProcessingEnvironment获取相关工具类,供后面处理注解时使用
    • getSupportedAnnotationTypes:是用来返回我们当前要处理哪些注解,必须重写该方法
    • getSupportedSourceVersion:返回当前注解处理器支持的java版本,不重写该方法会出现警告
    • process:用于解析注解,并生成对应的java文件

3、创建名叫"library"的Android Library类型Module:主要用于存放运行时调用生成的那些类的方法

  • build.gradle:

    apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    android {
        compileSdkVersion 28
        buildToolsVersion "29.0.2"
    
    
        defaultConfig {
            minSdkVersion 15
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles 'consumer-rules.pro'
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.core:core-ktx:1.1.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test:runner:1.2.0'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    
        //引入自定义的注解,这里使用api是为了引入当前library的项目可以也可以直接使用到自定义的注解
        api project(':annotation')
    }
    
    
    • api project(‘:annotation’):引入自定义的注解,这里使用api是为了引入当前library的项目可以也可以直接使用到自定义的注解
  • AnnotationKit.kt:通过反射获取生成的类,并调用构造方法,从而绑定当前Activity中的View

    /**
     * 用于绑定组件
     *
     * @author zhujie
     * @date 2019-09-13
     * @time 18:32
     */
    class AnnotationKit {
        companion object {
            /**
             * 缓存构造方法
             */
            private val mCacheConstructor = HashMap<Class<*>, Constructor<*>?>()
    
            fun bind(activity: Activity) {
                //从缓存中读取构造方法
                var constructor = mCacheConstructor[activity.javaClass]
                if (constructor == null) {
                    synchronized(mCacheConstructor) {
                        if (constructor == null) {
                            try {
                                val fullClassName =
                                    GENERATE_CLASS_FULL_NAME_HEAD + activity::class.java.simpleName
                                //通过反射获取生成的类
                                val clazz = Class.forName(fullClassName)
                                //获取构造方法
                                constructor = clazz.getConstructor(activity.javaClass)
                                //存入缓存
                                mCacheConstructor[activity.javaClass] = constructor
                            } catch (e: Throwable) {
                                e.printStackTrace()
                            }
                        }
                    }
                }
                //反射调用构造方法
                constructor?.newInstance(activity)
    
    
            }
        }
    
    }
    

4、创建名叫"app"的Android Application类型Module:需要使用自定义的app项目

  • build.gradle:
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
//使用kapt处理注解处理器
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.agilezhu.annotationkit"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    //引入自定义注解library
    implementation project(':library')
    //使用kapt编译注解处理器
    kapt project(':compiler')
}

  • MainActivity.kt:使用注解@BindView(R.id.main_title_view),并调用AnnotationKit.bind(this)完成绑定操作
class MainActivity : AppCompatActivity() {
    @BindView(R.id.main_title_view)
    lateinit var mTitleView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

		//调用生成的代码,初始化view
        AnnotationKit.bind(this)

        if (mTitleView != null) {
            Toast.makeText(this, "BindView成功", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "BindView失败", Toast.LENGTH_SHORT).show()
        }
    }
}
  • 需要注意的是,我这个测试项目中使用了kapt,而不是apt,是因为项目中使用了kotlin,所以需要kotlin的apt,也就是kotlin-kapt
  • 编译后生成的代码位于:app/build/generated/source/kapt/debug/目录下的package:com.agilezhu.annotationkit.generate
  • 生成的代码:BindProxy$MainActivity.java
    package com.agilezhu.annotationkit.generate;
    
    import com.agilezhu.annotationkit.MainActivity;
    
    public final class BindProxy$MainActivity {
      public BindProxy$MainActivity(MainActivity mainactivity) {
        mainactivity.mTitleView = (android.widget.TextView) mainactivity.findViewById(2131165289);
      }
    }
    

其他问题

  • 如果出现以下警告:
    [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.agilezhu.compiler.BindProcessor (NON_INCREMENTAL), com.google.auto.service.processor.AutoServiceProcessor (NON_INCREMENTAL).	
    
    则在根目录下的gradle.properties文件中添加下面内容就就可以:
    #apt增量编译,加速编译效果
    kapt.incremental.apt=true
    
  • 如果出现编译失败:
    Execution failed for task ':lib:common:kaptDebugKotlin'.
    > A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
       > java.lang.reflect.InvocationTargetException (no error message)
    
    在gradle.properties文件中添加下面这段内容:
    kapt.incremental.apt = false
    kapt.include.compile.classpath=false
    kapt.use.worker.api=false
    
  • 如果出现找不到AbstractProcessor等类的情况,请删除该Module,重新创建Java or Kotlin Library类型项目,即使其他类型Module把build.gradle配置改成一模一样也不行!
  • 在这里插入图片描述

Demo下载地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 模拟ButterKnife BindView注解实现绑定view(KAPT使用教程) 的相关文章

  • 书房再次升级啦~~

    国庆长假 xff0c 在家里面一顿折腾 xff0c 墙全部重新粉刷 xff0c 书房 卧室 客厅三种不同颜色 书房的颜色是当时在装饰城的展厅里面偷偷扣的墙皮 xff0c 在多乐士店色卡里面对出来的 xff0c 哈哈 ps 这篇日志的照片是用
  • android 抓取LOG的几种命令

    通常调试时候需要抓取log信息 xff0c 下面几种通过ADB命令来抓取log的方法 xff1a USB连接上手机 xff0c 手机需要其他操作 xff1b 然后运行ADB工具 xff1b 输入不同的命令即可抓取对应的LOG信息 抓取rad
  • 基于K近邻法的手写数字图像识别

    数字图像处理课程论文 题目 xff1a 数字图像识别 摘要 模式识别 PatternRecognition 是一项借助计算机 xff0c 就人类对外部世界某一特定环境中的客体 过程和现象的识别功能 xff08 包括视觉 听觉 触觉 判断等
  • Android apk图片资源目录存放规则(drawable和mipmap的区别)

    mipmap mdpi 48 48 mipmap hdpi 72 72 mipmap xhdpi 96 96 mipmap xxhdpi 144 144 mipmap xxxhdpi 192 192 drawable ldpi xff1a
  • 动态代理实现AOP

    阅读目录 代理静态代理动态代理动态代理的应用场景AOPAOP实例1AOP实例2 回到顶部 代理 代理顾名思义 xff1a 代为处理 不是对目标对象的直接操作 xff0c 而是通过代理对目标对象进行包装 xff0c 此时可以在目标对象的基础上
  • 使用linux系统提供的信号量集和共享内存实现生产者和消费者问题

    使用linux系统提供的信号量集和共享内存实现生产者和消费者问题 实验目的 了解和熟悉linux系统下的信号量集和共享内存 实验任务 使用linux系统提供的信号量集和共享内存实现生产者和消费者问题 实验要求 1 写两个程序 xff0c 一
  • XML学习准备(HTML、CSS、JavaScript)

    1 学习前的准备html 1 每个网站开发人员都有必要了解以下几方面的知识 xff1a 万维网如何工作 HTML 语言 如何使用层叠样式表 CSS JavaScript 编程 XML 标准 服务器脚本技术 使用 SQL 来管理数据 2 HT
  • 如何判断系统是32位的还是64位的?

    1 最简单的一种方法 xff1a xff08 1 xff09 XP系统 右击 我的电脑 34 属性 34 xff0c 如果是32为系统 xff0c 则显示 Microsoft Windows XP Professional xff0c 即不
  • No embedded stylesheet instruction for file:奇怪的错误

    今天在看Spring的一些东西 xff0c 刚开始看 xff0c 编写了一个HelloWorld xff0c 中间除了不少错误 xff0c 但是都排除了 xff1b 但是 xff0c 最后的一个错误实在是摸不着头脑 xff1a 21 11
  • 修改Windows的默认文件查看方式

    Windows文件夹中查看文件的方式有 xff1a 缩略图 xff0c 平铺 xff0c 列表 xff0c 图标 xff0c 详细信息 xff0c 默认方式为平铺 那么如何修改成其它的默认方式呢 xff1a 进入一个文件夹 xff0c 选择
  • windows右键添加cmd

    1 运行regedit打开注册表 2 在注册表 HKEY CLASSES ROOT Directory shell分支下新建一项命名为 CommandPrompt xff0c 修改右侧窗口中的 默认 键 值为 命令提示符 xff08 你想要
  • 如何查看端口被哪个程序占用

    假如我们需要确定谁占用了我们的3306端口 1 Windows平台 在windows命令行窗口下执行 xff1a C gt netstat aon findstr 34 3306 34 TCP 127 0 0 1 9050 0 0 0 0
  • 写个心得

    唉 xff0c 菜鸟就是悲哀啊 刚刚花了将近一个小时来学习for命令 xff0c 以前一看这么复杂 xff0c 就不想学了 xff0c 总想着虽然复杂 xff0c 但也是简单的 xff0c 将来用的时候 xff0c 查查就知道了 就这样 x
  • 视图绑定功能

    文章目录 前言 以下为Kotlin语言环境在android官方文档中 视图绑定可以用于替代findViewById 并且 当启用视图绑定之后 系统会为项目中的每个XML文件生成一个绑定类 绑定类的实例包含其布局中的所有具有ID的直接引用 一
  • Android 一键分享功能

    之前在做项目时遇到这么个需求 xff0c 就是用户点击Menu或者一个按钮可以把文字分享到各大微博例如新浪微博 腾讯 人人 开心 校内等 现在我给大家演示一下 xff08 一 xff09 先建一个工程文件ShareDemo xff08 二
  • 怎么根据Comparable方法中的compareTo方法的返回值的正负 判断升序 还是 降序?

    public int compareTo Student o return this age o age 比较年龄 年龄的升序 应该理解成return 1 this age o age 计算机没有所谓的正序和逆序 xff0c 他只管大还是小
  • 自动驾驶技术-环境感知篇:V2X技术的介绍

    V2X技术概述 在前面的几篇文章分别介绍了自动驾驶在环境感知领域的相关技术点 xff0c 主要介绍了如何通过雷达配合视觉技术实现车辆自身的智能 其实在环境感知方面 xff0c 除了利用车辆自身的智能 xff0c 还可以借助外部环境实现信息的
  • 深度学习分布式策略优化、显存优化、通信优化、编译优化综述

    综述 因为我个人最近在从事可能是AI领域对性能挑战最大的方向 xff0c 自动驾驶领域 xff0c 所以对整个深度学习训练的优化尤为关注 xff0c 最近一直在学习相关内容 xff0c 谨以此篇文章做一个总结 我一直很看好深度学习训练优化这
  • 量子计算机的优势和建设挑战

    1 为什么需要量子计算机 目前大家日常使用的计算机都是经典计算机 xff0c 经典计算机计算性能的发展遵循摩尔定律 xff0c 在价格不变时 xff0c 集成电路上可容纳的晶体管数目 xff0c 约每隔18个月便会增加一倍 性能也将提升一倍
  • 浅谈ChatGPT对生产关系及工具的颠覆影响

    xff08 先歪个楼 xff0c 配图是三体乱纪元 xff0c 证明三体问题无解 xff0c 而ChatGPT证明了AIGC问题是可解的 xff09 最近ChatGPT越来越热 xff0c 仿佛看到了资本市场又一次的爆发 最近周末也会跟几个

随机推荐

  • 蓟门边studio-码农创业路的起点

    蓟门边工作室 xff0c 开张了 先简单介绍下lz的情况吧 xff01 lz是北邮在读硕士 xff0c 码农一枚 断断续续写代码也有一两个年头了 xff0c 但是感觉总是在外面飘着 xff0c 没写过什么大的项目 xff0c 也没真正依靠技
  • 【机器学习算法-python实现】逻辑回归的实现(LogicalRegression)

    转载请注明出处 xff1a http blog csdn net buptgshengod 1 背景知识 在刚刚结束的天猫大数据s1比赛中 xff0c 逻辑回归是大家都普遍使用且效果不错的一种算法 xff08 1 xff09 回归 先来说说
  • 新闻个性化推荐系统(python)-(附源码 数据集)

    1 背景 最近参加了一个评测 xff0c 是关于新闻个性化推荐 说白了就是给你一个人的浏览记录 xff0c 预测他下一次的浏览记录 花了一周时间写了一个集成系统 xff0c 可以一键推荐新闻 xff0c 但是准确率比较不理想 xff0c 所
  • 明天是我的生日,写给24岁的自己

    哎 xff0c 本来想把今晚留给蛋疼的latex 我的导师让我写一篇论文 xff0c 我正在研究怎么用latex 但是想了想 xff0c 明天就过生日了 xff0c 最后一晚还是写点东西 xff0c 静静地思考下 本来想写点东西发到朋友圈或
  • 如何用PYTHON代码写出音乐

    如何用PYTHON代码写出音乐 什么是MIDI 博主本人虽然五音不全 xff0c 而且唱歌还很难听 xff0c 但是还是非常喜欢听歌的 我一直在做这样的尝试 xff0c 就是通过人工智能算法实现机器自动的作词和编曲 xff08 在这里预告下
  • 深度学习RNN实现股票预测实战(附数据、代码)

    背景知识 最近再看一些量化交易相关的材料 xff0c 偶然在网上看到了一个关于用 RNN实现股票预测的文章 xff0c 出于好奇心把文章中介绍的代码在本地跑了一遍 xff0c 发现可以 work 于是就花了两个晚上的时间学习了下代码 xff
  • Oracle面试题(基础篇)

    1 Oracle跟SQL Server 2005的区别 xff1f 宏观上 xff1a 1 最大的区别在于平台 xff0c oracle可以运行在不同的平台上 xff0c sql server只能运行在windows平台上 xff0c 由于
  • 图像拐点检测-原理以及代码实现

    今天带来的内容只用两个字形容 干货 xff01 xff01 首先我们科普下图像识别的常识 xff0c 图片在电脑看来 xff0c 其实就是一个矩阵 xff0c 每个矩阵中的一个值都对应图片的一个像素点 xff08 下图摘自 机器学习实践应用
  • c#如何实现在两个窗体(Form)间传输数据或变量

    在父窗体中显示子窗体时 xff0c 加上子窗体 Owner 61 this 在子窗体中定义一个父窗体对象 xff0c 在Load函数里面让父窗体对象 61 xff08 父窗体类型 xff09 this Owner 然后用这个父窗体对象就可以
  • 各类远程工具对比

    windows上自带远程桌面和远程协助 xff0c linux上基本标配SSH xff0c 当我们需要跨平台远程时 xff0c 可能就需要其他工具了 常用的工具如下 工具名称 支持平台 官网 特点 优点 缺点 ToDeskwindowsht
  • 无线电波在介质中的传播速度计算公式和印刷电路板(PCB)的特性阻抗与特性阻抗控制

    无线电波在介质中的传播速度计算公式 首先介绍介质天线 xff0c 介质天线是为了使接收天线小型化而设计的 所以有必要了解一下电磁波在不同介质中的传播速度的计算方法 xff0c 以便于确定不同介质材料中天线振子的长度 无线电波的波长 61 传
  • 新手如何快速入门人工智能?

    网上有很多关于 新手如何快速入门人工智能 的文章 xff0c 但是对于真正的小白来说并没有太多的指导作用 作为新手想进入人工智能领域 xff0c 首先是要明确需要掌握哪些基础知识 xff0c 其次是掌握一套行之有效的学习方法 xff0c 最
  • AI技术在医学领域有什么用?

    伴随人工智能技术热潮的再次崛起 xff0c 医学领域与AI技术的结合被认为是最有发展潜力的领域 可以看到人工智能多年来的厚积薄发 xff0c 在人脸语音识别 深度学习等领域的深入发展 xff0c 使得AI技术在医学领域不断获得突破性进展 下
  • OpenCV的使用范围有哪些?可以用在哪些领域?

    OpenCV是一种开源的计算机视觉库 xff0c 可以用于各种图像处理和计算机视觉任务 以下是OpenCV的使用范围 xff1a OpenCV的使用范围有哪些 计算机视觉 xff1a OpenCV可用于计算机视觉任务 xff0c 如目标检测
  • 人工智能可以用来诊断疾病吗?具体有什么用?

    人工智能可以用来诊断疾病 在医疗保健领域 xff0c 利用人工智能技术进行疾病诊断已经得到了广泛的应用和研究 具体来说 xff0c 人工智能可以通过机器学习等技术对医疗数据进行分析 xff0c 辅助医生进行疾病诊断 xff0c 提高诊断的准
  • 人工智能入门需要学习哪些课程?AI基础知识

    要入门人工智能 xff0c 需要掌握一些基础知识和技能 以下是一些入门人工智能需要学习的课程和基础知识 xff1a 人工智能入门需要学习哪些课程 xff1f AI基础知识 数学基础 xff1a 人工智能需要用到许多数学知识 xff0c 包括
  • coreApp="true";android:sharedUserId="android.uid.system";android:process="system"

    1 本文说下coreApp 61 34 true 34 xff1b android sharedUserId 61 34 android uid system 34 xff1b android process 61 34 system 34
  • 线程池 多线程运行结束后 如何关闭? ExecutorService的正确关闭方法

    前言 最近在使用ExecutorService的时候 xff0c 对于与ExecutorService相关的概念有些迷糊 xff0c 加上本身ExecutorService内部的有些方法名在取名上也容易让使用者误解 xff0c 导致 犯了一
  • 教你搭建个人/企业私有云盘-seafile

    工具 原料 Linux服务器 xff08 网上一大堆 xff0c 不要虚拟注意 xff0c 要云主机 xff0c 这样你才有操作权限 xff01 小编的是89一个月的测试主机 xff09 xshell xff08 以前的教程中用到过 xff
  • Android 模拟ButterKnife BindView注解实现绑定view(KAPT使用教程)

    简单回顾一下ButterKnife使用BindView注解初始化View过程 xff1a 使用注解 64 BindView R id main title view xff0c 并将要绑定的id传入调用BufferKnife bind th