Android Gradle开发、应用、插件发布(六)—实现打包自动复制文件插件

news2025/3/15 1:14:49

1. 前言

项目中遇到了一个问题 :

其中一个模块MyLibrary的assets文件夹中,需要存放很多文件(每个文件对应一个功能)。

这样导致的问题是MyLibrary打出的这个aar包体积特别大。

如果把MyLibrary严谨地拆解成若干个Module又比较费时,对于现在业务现状来说也显得没那么必要。

那么能不能在上传MyLibrary这个aar的时候,自动复制相应的文件到assets目录下,打出不同功能的aar呢 ?

这就需要自己开发一个Gradle插件来完成这个功能了。

本文环境

  • Android Studio 版本 : Android Studio Hedgehog | 2023.1.1
  • Gradle版本 : gradle-8.2
  • AGP版本 : 8.2.0
  • 项目结构 : 项目有app模块和MyLibrary模块,使用build.gradle (Groovy语言),app的assets目录下,有test1.so、test2.so、test3.so这三个文件

2. 配置上传Maven仓库

首先我们把MyLibrary配置上传Maven的插件,也就是maven-publish。

对于这部分功能不了解的同学可以先看我的这篇博客 : Android Module上传到Maven仓库 及 实现同时上传到多个Maven仓库

下面我们简单讲述一下

复制maven_upload.gradle到项目根目录下

apply plugin: 'maven-publish'

//TODO 这里填你Maven仓库的地址
def RELEASE_REPOSITORY_URL = "https://devops-maven.xxxxx.com/repository/yyyyy/"
//TODO 这里填你Maven仓库的账号
def NEXUS_USERNAME = "*********"
//TODO 这里填你Maven仓库的密码
def NEXUS_PASSWORD = "*********"

afterEvaluate {
   
    publishing {
   
        repositories {
   
            maven {
   
                name("ReleaseMaven")
                url = RELEASE_REPOSITORY_URL
                credentials {
   
                    username = NEXUS_USERNAME
                    password = NEXUS_PASSWORD
                }
            }
        }
        publications {
   
            Production(MavenPublication) {
   
                from components.release
                groupId = rootProject.ext.GROUP
                artifactId = rootProject.ext.POM_ARTIFACT_ID
                version = rootProject.ext.VERSION_NAME
            }
        }
    }
}

在MyLibrary中依赖

rootProject.ext.GROUP = "com.heiko.mytest"
rootProject.ext.POM_ARTIFACT_ID = "mylibrary"
rootProject.ext.VERSION_NAME = "1.0.0"
apply from: "${
     project.rootDir}/maven_upload.gradle"

Sync一下,可以看到gradle中多了publishing这个文件夹,里面的publishProductionPublicationToReleaseMavenRepository就是用来将MyLibrary打包并上传到Maven仓库的Gradle命令了。

3. Gradle相关操作

3.1 复制文件

from是原目录,into是目标目录,include可以指定需要复制的文件,onlyIf可以用来判断是否执行复制任务。

task copyFiles(type: Copy) {
   
    from 'src/main/assets'
    into 'build/outputs/assets'
    //include 'test1.txt','test2.txt' //指定文件名
    include '**/*.txt' //根据*匹配符合要求的文件
    onlyIf {
   
         true
    }
}
3.2 删除文件
task myDeleteFilesInDir(type: Delete) {
   
	//delete 'src/main/assets/test1.txt'  //删除test1.txt
	//delete 'src/main/assets/test1.txt' //删除文件夹下所有的文件,assets文件也会被删除
    delete fileTree('src/main/assets') //删除文件夹下所有的文件,assets文件不会被删除
}

3.3 dependsOn

dependsOn表示一个任务需要另一个任务先完成,可以理解为依赖于或需要先做,这意味着在执行这个任务之前,它所依赖的任务必须首先执行。

例如,如果你有一个任务叫做compile,它需要在clean任务之后执行,就可以像这样声明依赖关系:

task clearTask {
   
    doLast {
   
        println("执行 clear.doLast")
    }
}

task compileTask {
   
    doLast {
   
        println("执行 compile.dolast")
    }
}

compileTask.dependsOn(clearTask)

这样,每当你运行compile任务时,Gradle会首先运行clean任务。


执行compile任务的日志如下

> Task :app:clearTask
执行 clear.doLast

> Task :app:compileTask
执行 compile.dolast

BUILD SUCCESSFUL in 510ms
 

更通俗的理解 :
dependsOn就像是做饭的顺序:你首先需要准备食材,然后才能开始烹饪。同样地,如果你有一个任务依赖于另一个任务,那么你需要在开始当前任务之前先完成那个依赖任务。如果没有这种依赖关系,那么任务可能会在错误的时机执行,导致结果不正确或者出现错误。

//准备食材任务
task prepareFood(){
   }

//做饭任务
task cooking(){
   }

//做饭任务 依赖于 准备食材任务
cooking.dependsOn(prepareFood)
3.4 finalizedBy

finalizedBy用于指定一个任务另一个任务完成之后执行,可以理解为在完成后执行

Gradle中,finalizedBy用于指定一个或多个任务,这些任务将在关联任务执行完毕后执行,无论关联任务是否成功。可以把这个理解为一种清理或收尾的工作。

举个例子,我们有一个任务A,它被finalizedBy任务B,那就意味着在任务A执行完之后,无论任务A是否成功,任务B都会被执行。

这就好比一个厨师在做完一道菜(任务A)之后,无论这道菜是否做得成功,他都需要清理厨房(任务B),那么清理厨房这个步骤就是做菜这个任务的finalizedBy

3.5 mustRunAfter和shouldRunAfter

Gradle中,mustRunAfter 是用来定义任务执行的顺序的。如果你有两个任务,比如说任务A任务B,你希望无论何时,只要这两个任务都被执行任务A都必须在任务B之后执行,那么你就可以在任务A上调用 mustRunAfter 方法并传入任务B

举个例子,如下代码:

taskA.mustRunAfter taskB

这段代码的意思就是,如果这两个任务都在执行队列中,那么无论何时,任务A都必须在任务B之后执行。

需要注意的是,mustRunAfter 不会强制执行任务B。如果任务B没有被加入到执行队列中,那么任务A也可以独立执行。同样,如果任务A没有被加入到执行队列中,那么任务B也可以独立执行。

这与 dependsOn 不同。dependsOn 会创建一个强制的依赖关系,也就是说,任务A依赖于任务B,那么只要任务A需要执行,任务B就必须要先执行。而 mustRunAfter 只是定义了一个任务的执行顺序,并不会创建一个依赖关系。

shouldRunAftermustRunAfter是一样的,只不过mustRunAfter如果导致了循环依赖,Gradle 会抛出一个错误,并导致构建失败。而shouldRunAfter不会抛出异常,Gradle会忽略这个顺序要求,不会导致构建失败。

4. 新建插件

4.1 创建插件类

MyLibrarybuild.gradle中,增加如下代码,创建插件类 : MyPlugin

class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println "---- MyPlugin.apply ----"

		project.afterEvaluate { 
			//在Gradle项目配置阶段完成后会被调用
			//后续代码放在这里...
		}
    }
}
4.2 应用这个插件

MyLibrarybuild.gradle中添加这行代码,表示应用这个插件

apply plugin : MyPlugin

 在Sync一下,可以看到会打印出日志,表明我们配置这个插件成功了

---- MyPlugin.apply ----

5. 实现初步的依赖

5.1 在app的assets中添加文件

appassets目录下,添加test1.sotest2.sotest3.so这三个文件

5.2 MyLibrary新建assets目录

MyLibrary下新建assets文件夹

5.3 在MyPlugin中实现复制文件的Task

注意 : 后面的这些操作,代码都放在MyPluginapply方法的project.afterEvaluate {}

def myCopyFiles = project.task("myPublishCopyTask", type: Copy) {
        from project.fileTree('../app/src/main/assets')
        include 'test1.so', 'test2.so'
        into 'src/main/assets'
}
myCopyFiles.group = "publishingV2"

Sync一下项目,可以在Android StudioGradle Tab中,看到publishingV2文件夹,里面有myPublishCopyTask这个Task。


我们点击这个Task,可以发现,appassets目录下的test1.sotest2.so已经被复制到MyLibraryassets目录下了

5.4 在MyPlugin中实现删除文件的Task
//原本task名叫做myPublishDeleteTask,但是后面关联了其他Task后,这个task的名称起做publishAutoCopyTask是比较合适的
def myDeleteFilesInDir = project.task("publishAutoCopyTask", type: Delete) {
    delete project.fileTree('src/main/assets')
}
myDeleteFilesInDir.group = "publishingV2"

Sync一下项目,可以在Android StudioGradle Tab中,看到publishingV2文件夹,里面有publishAutoCopy这个Task
我们点击publishAutoCopy这个Task,可以发现,MyLibraryassets目录下的test1.sotest2.so已经被删除了

5.5 关联Task

这里,我们调用packageReleaseAssets.mustRunAfter(myCopyFiles),表明调用packageReleaseAssets之前,必定会调用myCopyFiles,否则就会抛出异常。
然后调用myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask),表示运行myDeleteFilesInDir的时候,会先去执行myCopyFilespublishTask

def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
def publishTask = "publishProductionPublicationToReleaseMavenRepository"
packageReleaseAssets.mustRunAfter(myCopyFiles)
myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
5.6 来看一下整体的代码

这部分代码都在MyLibrarybuild.gradle

rootProject.ext.GROUP = "com.heiko.mytest"
rootProject.ext.POM_ARTIFACT_ID = "mylibrary"
rootProject.ext.VERSION_NAME = "1.0.0"
apply from: "${project.rootDir}/maven_upload.gradle"

class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        println "---- MyPlugin.apply ----"

        project.afterEvaluate {
            def myCopyFiles = project.task("myPublishCopyTask", type: Copy) {
                from project.fileTree('../app/src/main/assets')
                include 'test1.so', 'test2.so'
                into 'src/main/assets'
            }
            myCopyFiles.group = "publishingV2"

            //原本task名叫做myPublishDeleteTask,但是后面关联了其他Task后,这个task的名称起做publishAutoCopy是比较合适的
            def myDeleteFilesInDir = project.task("publishAutoCopyTask", type: Delete) {
                delete project.fileTree('src/main/assets')
            }
            myDeleteFilesInDir.group = "publishingV2"

            def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
            def publishTask = "publishProductionPublicationToReleaseMavenRepository"
            packageReleaseAssets.mustRunAfter(myCopyFiles)
            myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
        }
    }
}

apply plugin: MyPlugin
5.7 调用myPublishDeleteTask

这个时候,我们在Gradle中执行myPublishDeleteTask这个任务,会发现 自动复制文件 -> 打包 -> 上传到Maven仓库 -> 删除文件这个链路,都自动执行了。

5.7.1 查看Maven仓库

我们来查看下Maven仓库,可以看到已经上传到maven仓库了
在这里插入图片描述

5.7.2 下载aar文件

下载这个版本的aar文件

接着拖到Android Studio中,就可以看到assets里已经有test1.sotest2.so

6. 实现动态配置

接着我们就可以来实现动态的配置了。
要实现动态配置,就需要在appbuild.gradle中配置名称和要复制的文件名称,像下面这样 :

apply plugin: MyPlugin

publishAutoCopy {
    //要复制的源文件目录
    sourcePath = '../app/src/main/assets'
    //要复制的目标文件目录
    targetPath = 'src/main/assets'

    publishItem {
        //名称
        name = 'weather'
        //要复制的文件名
        sourceFiles = ['test1.so', 'test2.so']
    }
    publishItem {
        //名称
        name = 'ocr'
        //要复制的文件名
        sourceFiles = ['test3.so']
    }
}

 那么需要怎么将这些配置传递到MyPlugin插件中呢 ?

6.1 编写传参类
class PublishAutoCopyItem {
    String name
    ArrayList<String> sourceFiles
}

class PublishAutoCopyExtension {
    private Project project
    String targetPath
    String sourcePath
    List<PublishAutoCopyItem> items = []

    PublishAutoCopyItem publishItem(Closure closure) {
        PublishAutoCopyItem myItem = new PublishAutoCopyItem()
        project.configure(myItem, closure)
        items << myItem
        return myItem
    }

    PublishAutoCopyExtension(Project project) {
        this.project = project
    }
}
6.2 在Plugin中获取传参
class MyPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        def extension = project.extensions.create("publishAutoCopy", PublishAutoCopyExtension)

        project.afterEvaluate {
            def sourcePath = extension.sourcePath ?: ""
            def targetPath = extension.targetPath ?: ""
            def items = extension.items

            println("sourcePath:" + sourcePath + " targetPath:" + targetPath)
            for (final def item in items) {
                println("item.name:" + item.name+" sourceFiles:"+item.sourceFiles)
            }

			//省略了之前写的代码...
        }
    }
}
6.3 在Build.gradle中配置

这个配置在MyLibrarybuild.gradle中即可

publishAutoCopy {
    //要复制的源文件目录
    sourcePath = '../app/src/main/assets'
    //要复制的目标文件目录
    targetPath = 'src/main/assets'

    publishItem {
        //名称
        name = 'weather'
        //要复制的文件名
        sourceFiles = ['test1.so', 'test2.so']
    }
    publishItem {
        //名称
        name = 'ocr'
        //要复制的文件名
        sourceFiles = ['test3.so']
    }
    //更多的配置可以在这里增加...
}
6.4 Sync下项目

然后我们Sync下项目,可以看到Gralde打印出了如下的日志

> Configure project :NcnnLibrary
sourcePath:../app/src/main/assets targetPath:src/main/assets
item.name:weather sourceFiles:[test1.so, test2.so]
item.name:ocr sourceFiles:[test3.so]

这样,我们就将配置传递给我们自定义的MyPlugin插件了

6.5 将参数传递给Task

将这些参数传递给Task,最终代码如下

project.afterEvaluate {
    def names = new ArrayList<>()
    def sourcePath = extension.sourcePath ?: ""
    def targetPath = extension.targetPath ?: ""

    for (final def item in extension.items) {
        def name = item.name ?: ""
        def sourceFiles = item.sourceFiles ?: [""]
        if (name.isEmpty()) return

        names.add(name)
        def nameCapitalize = name.capitalize()

        def myCopyFiles = project.task("myPublish${nameCapitalize}CopyTask", type: Copy) {
            from project.fileTree(sourcePath)
            include sourceFiles
            into targetPath
        }

        def myDeleteFilesInDir = project.task("publish${nameCapitalize}AutoCopy", type: Delete) {
            delete project.fileTree(targetPath)
        }
        myDeleteFilesInDir.group = "publishingv2"

        def packageReleaseAssets = project.tasks.findByName("packageReleaseAssets")
        def publishTask = "publish${nameCapitalize}PublicationToReleaseMavenRepository"
        packageReleaseAssets.mustRunAfter(myCopyFiles)
        myDeleteFilesInDir.dependsOn(myCopyFiles, publishTask)
    }

    project.rootProject.ext["${project.name}_NAMES"] = names
}

Sync项目后,可以发现有publishOcrAutoCopypublishWeatherAutoCopy两个Task

在这里插入图片描述

6.6 修改 maven_upload.gradle

myArtifactId现在通过rootProject.ext.POM_ARTIFACT_ID进行传递,所以我们现在需要修改maven_upload.gradle

apply plugin: 'maven-publish'

//TODO 这里填你Maven仓库的地址
def RELEASE_REPOSITORY_URL = "https://devops-maven.xxxxx.com/repository/yyyyy/"
//TODO 这里填你Maven仓库的账号
def NEXUS_USERNAME = "*********"
//TODO 这里填你Maven仓库的密码
def NEXUS_PASSWORD = "*********"

afterEvaluate {
    publishing {
        repositories {
            maven {
                name("ReleaseMaven")
                url = RELEASE_REPOSITORY_URL
                credentials {
                    username = NEXUS_USERNAME
                    password = NEXUS_PASSWORD
                }
            }
        }
        publications {
            List<String> names
            def namesKey = "${project.name}_NAMES"
            if (rootProject.ext.has(namesKey)) {
                names = rootProject.ext[namesKey]
            } else {
                names = new ArrayList()
                names.add("")
            }
            for (final def itemName in names) {
                def publicationName = itemName.capitalize()
                create(publicationName, MavenPublication) {
                    def myArtifactId
                    if (name.isEmpty()) myArtifactId = rootProject.ext.POM_ARTIFACT_ID
                    else myArtifactId = rootProject.ext.POM_ARTIFACT_ID + "-" + itemName

                    from components.release
                    groupId = rootProject.ext.GROUP
                    artifactId = myArtifactId
                    version = rootProject.ext.VERSION_NAME
                }
            }
        }
    }
}
6.7 动态配置完成

到这里,我们就完成动态配置了 : 我们想要打包带某些文件的aar,就点击对应的Task进行打包就行了

  • 想打带test1.sotest2.so的包,那么就点击publishWeatherAutoCopy
  • 想打带test3.so的包,那么就点击publishOcrAutoCopy

7. 将MyPlugin移到独立的gradle文件中

我们还可以将MyPlugin相关的代码移到一个单独的gradle文件中,比如publish_auto_copy.gradle,具体方式和maven_upload.gradle类似。

这样,我们就可以在build.gradle中,使用apply from: "${project.rootProject.rootDir}/publish_auto_copy.gradle"这一句代码,就应用MyPlugin插件了。接着,在build.gradle中配置好MyPlugin的参数就行了。

apply from: "${project.rootProject.rootDir}/publish_auto_copy.gradle"

publishAutoCopy {
    //要复制的源文件目录
    sourcePath = '../app/src/main/assets'
    //要复制的目标文件目录
    targetPath = 'src/main/assets'

    publishItem {
        //名称
        name = 'weather'
        //要复制的文件名
        sourceFiles = ['test1.so', 'test2.so']
    }
    publishItem {
        //名称
        name = 'ocr'
        //要复制的文件名
        sourceFiles = ['test3.so']
    }
    //更多的配置可以在这里增加...
}

至此,我们就完成了打包自动复制文件插件的全部功能了。

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

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

相关文章

HTML静态网页成品作业(HTML+CSS)——宠物狗介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有3个页面。 二、作品演示 三、代…

轻松调用其他工程的Python文件,提升编程效率

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、前言 在Python开发过程中&#xff0c;经常会遇到需要在一个工程中调用另一个工程的Python文件的情况。这种情况通常发生在需要复用已有代码或者进行模块化开发时。那么&#xff0c;如何实现这一目标呢&#xff…

JVM(三)

在上一篇中&#xff0c;介绍了JVM组件中的类加载器&#xff0c;以及相关的双亲委派机制。这一篇主要介绍运行时的数据区域 JVM架构图&#xff1a; JDK1.8后的内存结构&#xff1a; (图片来源&#xff1a;https://github.com/Seazean/JavaNote) 而在运行时数据区域中&#…

SpringBoot+Vue开发记录(五)-- 数据库设计

我去&#xff0c;时隔这么久又开始了QAQ。主要是还是自己太懒了。 本篇文章的主要内容是数据库设计。 先简单创建个数据库&#xff1a; 这是创建好了的&#xff1a; 一、数据库设计 先就做一个很简单的设计&#xff0c;里面就只有用户和题。 大概就这样&#xff1a; 二、创…

【学习笔记】Windows GDI绘图目录

题外话 不知几时开始&#xff0c;觉得学习过程中将内容记录下来&#xff0c;有助于加强记忆&#xff0c;还方便后续查找&#xff0c;顺便帮助有需要的人&#xff0c;更有来自您阅读、点赞、收藏和评论时给我带来的动力与兴奋。 目录 【学习笔记】Windows GDI绘图(一)图形概述…

如何从U盘恢复误删除的文件

在许多情况下&#xff0c;用户可能会发现其U盘上的数据误删&#xff0c;并且无法访问或恢复它。在这篇文章中&#xff0c;我们将看到如何使用命令提示符尝试从U盘恢复损坏的文件和数据。我们还将列出一些免费的U盘恢复软件及其独特的功能&#xff0c;以便在前一种方法无法产生所…

PVE 虚拟机环境下删除 local-lvm分区

1、删除逻辑卷 lvremote pve/data 2、扩展逻辑卷 lvextend -l 100%FREE -r pve/root 3、 修改存储目录内容 点击 Datacenter - Storage &#xff08;1&#xff09;删除local-lvm分区 &#xff08;2&#xff09;编辑local分区&#xff0c;在内容一项中勾选所有可选项。

unity开发Hololens,使用unity自带的UGUI

hololens 使用UGUI 新建画布&#xff0c;添加组件&#xff0c; 画布模式改成WorldSpace&#xff0c;这样在能在3D场景里 随意的移动位置&#xff0c; 添加NearIteractionTouchaBleUnityUI、CanvasUtility组件 EaventsToReceive改成Pointer&#xff0c; 这样&#xff0c;UGUI的…

Idea中flume的Interceptor的编写教程

1.新建-项目-新建项目 注意位置是将来打包文件存放的位置&#xff0c;即我们打包好的文件在这/export/data个目录下寻找 2. 在maven项目中导入依赖 Pom.xml文件中写入 <dependencies> <dependency> <groupId>org.apache.flume</groupId> <artifa…

渲染管线——应用阶段

知识必备——CPU和GPU 应用阶段都做了什么 应用阶段为渲染准备了什么 1.把不可见的数据剔除 2.准备好模型相关数据&#xff08;顶点、法线、切线、贴图、着色器等等&#xff09; 3.将数据加载到显存中 4.设置渲染状态&#xff08;设置网格需要使用哪个着色器、材质、光源属性等…

【NumPy】NumPy性能优化与内存管理:解锁高效编程的高级策略

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

《Ai学习笔记》自然语言处理 (Natural Language Processing):常见机器阅读理解模型(上)02

Glove 词向量&#xff1a; 在机器理解中的词的表示&#xff1a; 词袋&#xff08;bow,bag of words&#xff09; one-hot 词向量 word2vec glove 目的&#xff1a;将一个词转换成一个向量 Word2vec 是一种用于生成词向量的工具包&#xff0c;由Google在2013年开源推出…

HMI设计:再谈上位机与下位机,附海量案例图

上期回顾&#xff1a;HMI界面之&#xff1a;上位机界面设计&#xff0c;一文扫盲 一、上位机负责控制和决策&#xff0c;下位机负责采集和执行 上位机和下位机是两个概念&#xff0c;通常用于描述计算机系统中不同层次的设备或组件。 上位机&#xff08;Host Computer&#x…

vue3 vite动态根据字符串加载组件

1 原理 import.meta.glob() 其实不仅能接收一个字符串&#xff0c;还可以接收一个字符串数组&#xff0c;就是匹配多个位置 let RouterModules import.meta.glob(["/src/view/*/*.vue", "/src/view/*.vue"]);这样我们就拿到了相对路劲的组件对象&#xf…

【学习笔记】Windows GDI绘图(五)图形路径GraphicsPath详解(上)

文章目录 图形路径GraphicsPath填充模式FillMode构造函数GraphicsPath()GraphicsPath(FillMode)GraphicsPath(Point[],Byte[])和GraphicsPath(PointF[], Byte[])GraphicsPath(Point[], Byte[], FillMode)和GraphicsPath(PointF[], Byte[], FillMode)PathPointType 属性FillMode…

最新版npm详解

如&#xff1a;npm中搜索 jQuery image.png image.png 接地气的描述&#xff1a;npm 类似于如下各大手机应用市场 image.png image.png 查看本地 node 和 npm 是否安装成功 image.png image.png 或 npm install -g npm image.png image.png image.png image.png image.…

Spring Boot集成Picocli快速入门Demo

1.什么是Picocli&#xff1f; Picocli是一个单文件命令行解析框架&#xff0c;它允许您创建命令行应用而几乎不需要代码。使用 Option 或 Parameters 在您的应用中注释字段&#xff0c;Picocli将分别使用命令行选项和位置参数填充这些字段。使用Picocli来编写一个功能强大的命…

16.线性回归代码实现

线性回归的实操与理解 介绍 线性回归是一种广泛应用的统计方法&#xff0c;用于建模一个或多个自变量&#xff08;特征&#xff09;与因变量&#xff08;目标&#xff09;之间的线性关系。在机器学习和数据科学中&#xff0c;线性回归是许多入门者的第一个模型&#xff0c;它…

蓝桥杯Web开发【大学组:省赛】2022年真题

1.水果拼盘 目前 CSS3 中新增的 Flex 弹性布局已经成为前端页面布局的首选方案&#xff0c;本题可以使用 Flex 属性快速完成布局。 1.1 题目问题 建议使用 flex 相关属性完成 css/style.css 中的 TODO 部分。 禁止修改圆盘的位置和图片的大小。相同颜色的水果放在相同颜色的…

根据Depth Quality Tool的z轴误差值确认相机是否需要进行相机内参校准

下载Depth Quality Tool深度质量验证工具 网盘链接【RealSense SDK v2.55.1】 链接&#xff1a;https://pan.baidu.com/s/1NrlbwNDBUL8wpWfVwbpMwA?pwd2jl0 提取码&#xff1a;2jl0 打开Depth Quality Tool深度质量验证工具 找一面墙作为目标&#xff0c;将摄像头水平对准墙…