Soong 构建系统

背景

Soong 构建系统在Android 7.0开始引入,目的是取代Make。它利用Kati GNU Make 和Ninja构建系统组件来构建Android Soong是用Go语言写的,go环境在prebuilts/go环境下,Soong在编译时,解析bp文件,转化成Ninja文件,完成Android的选择编译,解析配置过程。 Soong相当于Makefile编译系统核心,即build/make/core。

Blueprint

Blueprint由Go语言编写,是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。

KATI

kati是Google专门为了Android而开发的一个小项目,基于Golang和C++。目的是为了把Android中的Makefile,转换成Ninja文件。 在最新的Android R(11)中,Google已经移除了/build/kati目录,只保留了一个预先编译出来的可执行文件:prebuilts/build-tools/linux-x86/bin/ckati kata是go语言写的,ckatai是C++写的。kati官方文档对它的描述是:kati is an experimental GNU make clone。也就是说,kati是对等make命令的。只不过kati并不执行具体的编译工作,而是生成ninja文件。kati刚开始是使用Golang编写的,但是后来验证下来发现编译速度不行,于是改成C++编写,所以现在存在两个版本:kati、ckati。

Ninja

Ninja 是Google的一名程序员推出的注重速度的构建工具。一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。 Ninja是一个致力于速度的小型编译系统(类似于Make),如果把其他编译系统比做高级语言的话,Ninja就是汇编语言。通常使用Kati或soong把makefile转换成Ninja files,然后用Ninja编译。 ninja核心是由C/C++编写的,同时有一部分辅助功能由python和shell实现。由于其开源性,所以可以利用ninja的开源代码进行各种个性化的编译定制。

Makefile

Makefile是一个文本文件,是GNU make程序在执行的时候默认读取的配置文件。其关系到了整个工程的编译规则。一个工程中的源文件按类型、功能、模块分别放在若干个目录中,makefile定义了一系列规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。 其好处在于:写好makefile之后,只需要一个“make”命令,整个工程就能完全自动编译,极大地提高了软件开发的效率。

Android 编译指令

source build/envsetup.sh //step 1.初始化编译环境
lunch xxx //step 2.选择编译目标
make -j8 //step 3.执行编译
pack -d -v //step 4.打包生成镜像

编译流程图

初始化编译环境

envsetup.sh脚本:主要是定义了make、lunch等相关函数,为Android系统的编译提供支持。

shell脚本部分

1.make 指令入口

@build/envsetup.sh
function make()
{
    _wrap_build $(get_make_command "$@") "$@"
}

2.获取构建方式(以前通过make,现在改成soong方式),通过判断soong_ui.bash文件是否存在,来决定系统构建方式。

@build/envsetup.sh
function get_make_command()
{
    # If we're in the top of an Android tree, use soong_ui.bash instead of make
    if [ -f build/soong/soong_ui.bash ]; then
        # Always use the real make if -C is passed in
        for arg in "$@"; do
            if [[ $arg == -C* ]]; then
                echo command make
                return
            fi
        done
        echo build/soong/soong_ui.bash --make-mode
    else
        echo command make
    fi
}

3.执行构建指令,并且打印建设时间,构建结果。

@build/make/shell_utils.sh
# Pretty print the build status and duration
function _wrap_build()
{
    if [[ "${ANDROID_QUIET_BUILD:-}" == true ]]; then
      "$@"
      return $?
    fi
    local start_time=$(date +"%s")
    "$@"
    local ret=$?
    local end_time=$(date +"%s")
    local tdiff=$(($end_time-$start_time))
    local hours=$(($tdiff / 3600 ))
    local mins=$((($tdiff % 3600) / 60))
    local secs=$(($tdiff % 60))
    local ncolors=$(tput colors 2>/dev/null)
    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
        color_failed=$'\E'"[0;31m"
        color_success=$'\E'"[0;32m"
        color_warning=$'\E'"[0;33m"
        color_reset=$'\E'"[00m"
    else
        color_failed=""
        color_success=""
        color_reset=""
    fi

    echo
    if [ $ret -eq 0 ] ; then
        echo -n "${color_success}#### build completed successfully "
    else
        echo -n "${color_failed}#### failed to build some targets "
    fi
    if [ $hours -gt 0 ] ; then
        printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs
    elif [ $mins -gt 0 ] ; then
        printf "(%02g:%02g (mm:ss))" $mins $secs
    elif [ $secs -gt 0 ] ; then
        printf "(%s seconds)" $secs
    fi
    echo " ####${color_reset}"
    echo
    return $ret
}

可以通过命令行可以看出,当编译完成时,显示构建结果

07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition vendor_a will resize from 0 bytes to 257830912 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition product_a will resize from 0 bytes to 1762189312 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition vendor_dlkm_a will resize from 0 bytes to 17870848 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition system_dlkm_a will resize from 0 bytes to 475136 bytes
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
2024-07-02 10:54:49 - build_super_image.py - INFO    : Done writing image out/target/product/xxx-xxx/super.img

#### build completed successfully (05:28 (mm:ss)) ####


#### build completed successfully (05:28 (mm:ss)) ####

执行soong_ui.bash脚本

soong_ui.bash脚本主要做了两件事: 1.根据"android/soong/cmd/soong_ui/"内容,生成soong_ui的go可执行程序,生成路径:out/soong_ui 2.执行soong_ui程序

@build/soong/soong_ui.bash
...
# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui  // 1.生成soong_ui执行程序
...
exec "$(getoutdir)/soong_ui" "$@"//2.执行soony_ui程序,启动构建

soong 入口

soong_ui是个go程序,至此进入soong构建系统的世界。

@build/soong/cmd/soong_ui/main.go
func main() {
...
    preProductConfigSetup(buildCtx, config)//创建文件遍历器
    if build.SetProductReleaseConfigMaps(buildCtx, config) {
        log.Verbose("Product release config maps found\n")
        config = freshConfig()
    }

    c.run(buildCtx, config, args)//启动构建
}
func preProductConfigSetup(buildCtx build.Context, config build.Config) {

...
    f := build.NewSourceFinder(buildCtx, config)// Create a source finder.
    defer f.Shutdown()
    build.FindSources(buildCtx, config, f)//遍历整个项目,记录所有mk,bp等文件
}

所有记录信息都在 out/.module_paths/Android.bp.list 和 out/.module_paths/Android.mk.list

soong构建系统

soong构建系统最核心的步骤。其主要通过将bp、mk文件,解析成ninja文件,再通过ninja去实现系统构建任务。

@build/soong/ui/build/build.go
func Build(ctx Context, config Config) {

    ...
    runSoong(ctx, config)//step 1.处理bp文件
    ...
    runKatiBuild(ctx, config)//step 2.处理mk文件
    ...
    createCombinedBuildNinjaFile(ctx, config)//step 3.整合ninja文件
    ...
    runNinja(ctx, config)//step 4.构建
    ...
}

1.runSoong

runSoong 对工具进行编译,先编译出blueprint等编译工具, 再把*.bp 编译成 out/soong/build.ninja。

@android\build\soong\ui\build\soong.go
func runSoong(ctx Context, config Config) {
    ...
    ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")//1.生成out/soong/bootstrap.ninja
    ...
    ctx.BeginTrace(metrics.RunSoong, "environment check")//2.初始环境检查
    ...
    ctx.BeginTrace(metrics.RunSoong, "bpglob")//3.创建bpglob可执行程序
    config.PrebuiltBuildTool("ninja"), ninjaArgs...) //提前编译好ninja可执行文件
    ...
    targets := make([]string, 0, 0)
    if config.JsonModuleGraph() {
        targets = append(targets, config.ModuleGraphFile())
    }
    if config.Queryview() {
        targets = append(targets, config.QueryviewMarkerFile())
    }
    if config.SoongDocs() {
        targets = append(targets, config.SoongDocsHtml())
    }
    if config.SoongBuildInvocationNeeded() {
        // This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
        targets = append(targets, config.SoongNinjaFile())
    }
    ninja(targets...)5.生成out/soong/build.ninja
    ...
}

bootstrap表示从无到有创建Soong,该阶段会先生成bootstrap相关的工具程序:,再使用编译生成的soong_build程序,生成out/soong/build.ninja文件。用于后续参与Ninja编译构建工作,可以编译终端看出,build.a523_pro_arm64.ninja是编译目标文件

我们通过verboase.log 调试信息也可以证明

[1/1] cd "$(dirname "out/host/linux-x86/bin/soong_build")" && BUILDER="$PWD/$(basename "out/host/linux-x86/bin/soong_build")" && cd / && env -i  "$BUILDER"     --top "$TOP"     --soong_out "out/soong"     --out "out"     --soong_variables out/soong/soong.a523_pro_arm64.variables -o out/soong/build.a523_pro_arm64.ninja --globListDir a523_pro_arm64 --globFile out/soong/globs-a523_pro_arm64.ninja -l out/.module_paths/Android.bp.list --available_env out/soong/soong.environment.available --used_env out/soong/soong.environment.used.a523_pro_arm64.build Android.bp

out/soong/build.a523_pro_arm64.ninja文件罗列了项目上所有的bp模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。 该阶段在编译时,控制台打印的log如下:

[100% 1032/1032] analyzing Android.bp files and generating ninja file at out/soong/build.a523_pro_arm64.ninja
cedarx-config: sdkVersion[35], board[saturn], platformconfig[YES], afbcMode[2], grfBuild[false]

runKatiBuild

runKatiBuild, 加载 build/make/core/main.mk, 搜集所有的Android.mk文件生成out/build-xxx.ninja文件

@build\soong\ui\build\kati.go
func runKatiBuild(ctx Context, config Config) {
    ctx.BeginTrace(metrics.RunKati, "kati build")
    ...
    args := []string{
        "--writable", config.OutDir() + "/",
        "-f", "build/make/core/main.mk",
    }
    ...
    runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})//执行ckati指令,构建mk
    ...
}

1.参考soong.log的日志,runKati函数最后会引用cKati指令,加载main.mk文件,生成ninja文件。其指令如下:

2024/07/04 15:26:08.038311 build/soong/ui/logger/logger.go:290: "ckati" executing "prebuilts/build-tools/linux-x86/bin/nsjail" [-x prebuilts/build-tools/linux-x86/bin/ckati -H android-build --cwd /home1/billyfeng/project/android15_A523 -t 0 -e --proc_rw -u nobody -g nogroup --rlimit_as soft --rlimit_core soft --rlimit_cpu soft --rlimit_fsize soft --rlimit_nofile soft -R / -B /tmp -B /home1/billyfeng/project/android15_A523 -B /home1/billyfeng/project/android15_A523/out --disable_clone_newcgroup -q -- --ninja --ninja_dir=out --ninja_suffix=-a523_pro_arm64 --no_ninja_prelude --use_ninja_phony_output --regen --ignore_optional_include=out/%.P --detect_android_echo --color_warnings --gen_all_targets --use_find_emulator --werror_find_emulator --no_builtin_rules --werror_suffix_rules --werror_real_to_phony --top_level_phony --werror_phony_looks_real --werror_writable --kati_stats --writable out/ --werror_implicit_rules -f build/make/core/main.mk --werror_overriding_commands SOONG_MAKEVARS_MK=out/soong/make_vars-a523_pro_arm64.mk SOONG_ANDROID_MK=out/soong/Android-a523_pro_arm64.mk TARGET_DEVICE_DIR=device/softwinner/saturn/a523-pro KATI_PACKAGE_MK_DIR=out/target/product/a523-pro/obj/CONFIG/kati_packaging]

2.build/make/core/main.mk是什么? 从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。

文件说明
build/make/core/main.mkBuild的主控文件,主要作用是包含其他mk,以及定义几个最重要的编译目标,同时检查编译工具的版本,例如gcc、clang、java等
build/make/core/config.mkBuild的配置文件,主要是区分各个产品的配置,并将这些编译器参数引入产品配置 BoardConfig.mk,同时也配置了一些编译器的路径等
build/make/core/clang/config.mkclang编译的配置文件
build/make/core/definitions.mk最重要的 Make 文件之一,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,关于这些函数的说明请参见每个函数的代码注释。
build/make/core/dex_preopt.mk定义了dex优化相关的路径和参数
build/make/core/pdk_config.mk编译pdk的配置文件
build/make/core/Makefile系统最终编译完成所需要的各种目标和规则
build/make/core/envsetup.mk包含进product_config.mk文件并且根据其内容设置编译产品所需要的环境变量,并检查合法性,指定输出路径等
build/make/core/combo/select.mk根据当前编译器的平台选择平台相关的 Make 文件
build/make/core/ninja_config.mk解析makefile的的列表,传给kati,配置传给ninja和kati的目标
build/make/core/soong_config.mk配置soong的环境变量,建立go变量和mk变量的json映射关系,让go变量可以获取到mk中定义的变量值

3.在启动时,就会搜索项目中所有Android.mk文件,并且记录在并记录于out/.module_paths/Android.mk.list文件。在main.mk里面,便可以根据这个文件,将所有的内容include进来。因此,在该项目下定义的任一Android.mk都可以被引用。

@build/make/core/main.mk
#
# Include all of the makefiles in the system
#
subdir_makefiles := $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk $(SOONG_ANDROID_MK)
# Android.mk files are only used on Linux builds, Mac only supports Android.bp
ifeq ($(HOST_OS),linux)
  subdir_makefiles += $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
endif
subdir_makefiles += $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk
subdir_makefiles_total := $(words int $(subdir_makefiles) post finish)
.KATI_READONLY := subdir_makefiles_total

$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))//遍历所有mk文件

(4)main.mk加载完成后,最终生成out/build-xxx.ninja文件,用于后续参与Ninja编译构建工作。out/build-xxx.ninja文件罗列了项目上所有的mk模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。 在Android15生成以下.mk文件。

./build-xxx_pro_arm64-cleanspec.ninja
./build-xxx_pro_arm64-package.ninja
./build-xxx_pro_arm64.ninja

合并 ninja文件 ---createCombinedBuildNinjaFile

为了方便统一管理,Soong将out/soong/build.ninja文件 、out/build-.ninja文件和out/build--package.ninja文件, 合成为out/combined-*.ninja文件,由该文件记录所有待执行ninja文件。

var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
builddir = {{.OutDir}}
{{if .UseRemoteBuild }}pool local_pool
 depth = {{.Parallel}}
{{end -}}
pool highmem_pool
 depth = {{.HighmemParallel}}
{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}//追加out/build-*.ninja
subninja {{.KatiPackageNinjaFile}}//追加文件out/build-*-package.ninja
{{end -}}
subninja {{.SoongNinjaFile}}//追加文件out/soong/build-xxx.ninja
`))

func createCombinedBuildNinjaFile(ctx Context, config Config) {
    ...
    file, err := os.Create(config.CombinedNinjaFile())//创建combined-*.ninja文件
    ...
    if err := combinedBuildNinjaTemplate.Execute(file, config); //执行合并动作
    ...
}

例如,在 combined-a523_pro_arm64.ninja项目中,我们可以看到包含待执行ninja文件。

builddir = out
pool highmem_pool
 depth = 7
subninja out/build-xxx_pro_arm64.ninja
subninja out/build-xxx_pro_arm64-package.ninja
subninja out/soong/build.xxx_pro_arm64.ninja

soong编译所产生文件

文件备注
android/out/soong.logsoong模块打印内容
android/out/verbose.log控制台编译日志
android/out/dumpvars-verbose.loglunch的log信息
android/out/.ninja_logninja模块编译log
android/out/soong_uigo可执行程序,执行soong编译
android/out/.module_paths/遍历整个项目,记录所有的mk、bp等文件
android/out/soong/build.ninja项目上所有bp模块的编译规则
android/out/build-*.ninja项目上所有mk模块的编译规则
android/out/combined-*.ninja项目上所有模块的编译规则组合
android/out/soong/host/linux-x86/bin/androidmkmk文件转bp文件的指令

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

互联网留给网站建设的,也就一个门缝了,抓紧往高端进发吧。

高端定制网站具有以下价值&#xff1a; 独特性&#xff1a;高端定制网站能够根据企业的品牌形象和定位进行设计&#xff0c;呈现独特的风格和用户体验。这有助于提升企业的品牌形象和差异化竞争力&#xff0c;使企业在竞争激烈的市场中脱颖而出。用户体验&#xff1a;高端定制…

vue-使用Worker实现多标签页共享一个WebSocket

文章目录 前言一、SharedWorker 是什么SharedWorker 是什么SharedWorker 的使用方式SharedWorker 标识与独占 二、Demo使用三、使用SharedWorker实现WebSocket共享 前言 最近有一个需求&#xff0c;需要实现用户系统消息时时提醒功能。第一时间就是想用WebSocket进行长连接。但…

14-47 剑和诗人21 - 2024年如何打造AI创业公司

​​​​​ 2024 年&#xff0c;随着人工智能继续快速发展并融入几乎所有行业&#xff0c;创建一家人工智能初创公司将带来巨大的机遇。然而&#xff0c;在吸引资金、招聘人才、开发专有技术以及将产品推向市场方面&#xff0c;人工智能初创公司也面临着相当大的挑战。 让我来…

下一代 CLI 工具,使用Go语言用于构建令人惊叹的网络应用程序

大家好&#xff0c;今天给大家分享一个创新的命令行工具Gowebly CLI&#xff0c;它专注于使用Go语言来快速构建现代Web应用程序。 Gowebly CLI 是一款免费开源软件&#xff0c;有助于在后端使用 Go、在前端使用 htmx 和 hyperscript 以及最流行的 CSS 框架轻松构建令人惊叹的 W…

Maven Nexus3 私服搭建、配置、项目发布指南

maven nexus私服搭建 访问nexus3官方镜像库,选择需要的版本下载:Docker Nexus docker pull sonatype/nexus3:3.49.0 创建数据目录并赋权 sudo mkdir /nexus-data && sudo chown -R 200 /nexus-data 运行(数据目录选择硬盘大的卷进行挂载) docker run -d -p 808…

AI集成工具平台一站式体验,零门槛使用国内外主流大模型

目录 0 写在前面1 AI艺术大师1.1 绘画制图1.2 智能作曲 2 AI科研助理2.1 学术搜索2.2 自动代码 3 AI智能对话3.1 聊天机器人3.2 模型竞技场 4 特别福利 0 写在前面 人工智能大模型浪潮滚滚&#xff0c;正推动着千行百业的数智化进程。随着技术演进&#xff0c;2024年被视为是大…

数据库开发:mysql基础一

文章目录 数据库开发Day15&#xff1a;MySQL基础&#xff08;一&#xff09;一、MySQL介绍与安装【1】MySQL介绍&#xff08;5&#xff09;启动MySQL服务&#xff08;6&#xff09;修改root登陆密码 二、SQL简介三、数据库操作四、数据表操作4.1、数据库数据类型4.2、创建数据表…

tomcat原理、结构、设计模式

1 what 一种web服务器&#xff0c;运行java servlet、jsp技术&#xff0c;能为java web提供运行环境并通过http协议处理客户端请求。即tomcat http服务器 servlet容器。同类产品有jetty Web应用&#xff1a;Web应用是指通过Web浏览器访问的应用程序&#xff0c;它使用Web技术…

c#类型转换和常见集合类型

目录 1. 整数转换&#xff0c;整数和字符串&#xff0c;字符串和整数之间的转换怎么实现&#xff1f; 2. 日期转换&#xff0c;获取当前日期&#xff0c;字符串转日期&#xff0c;日期转字符串怎么实现&#xff1f; 3. 举例一维、二维、三维数组 4. 需求&#xff1a;有个88…

【嵌入式单片机】之RS-232、RS-485、RS-422比较

1. RS422是什么 RS422,正式名称为TIA/EIA-422,是一种串行通信标准,专为实现长距离、高可靠性的数据传输而设计。它采用差分信号传输技术,通过两对双绞线实现全双工通信,即发送和接收可以同时进行。RS422在工业自动化和远程监控系统中曾经扮演着重要角色,以其出色的抗干扰…

p标签文本段落中因编辑器换行引起的空格问题完美解决方案

目录 1.修改前的代码&#xff1a;2.修改后的代码3.总结 在HTML文档中&#xff0c;如何要在&#xff08;p标签&#xff09;内写一段很长的文本段落&#xff0c;并且没有 换行。由于IDE或者编辑器界面大小有限或需要在vue中逻辑处理动态显示文本&#xff0c;一行写完太长&#x…

14-46 剑和诗人20 – 减少幻觉的提示词工程

​​​​​ 概述 幻觉或“编造”是大型语言模型 (LLM) 的常见故障模式&#xff0c;它们会产生事实上不正确或无意义的内容。幻觉背后的一些主要原因是&#xff1a; 当模型不确定真正的答案时&#xff0c;它会试图通过捏造信息来提供过度的帮助。该模型缺乏适当的基础、背景和…

YOLOv5、v7、v8如何修改检测框文字颜色和大小

YOLOv5和YOLOv8默认的标签文字颜色为白色&#xff0c;但是在亮度较大的图片中文字不明显&#xff0c;就需要对标签文字的颜色进行修改 一、YOLOv5 打开X:\Anaconda\envs\your-env\Lib\site-packages\ultralytics\utils\plotting.py X代表你的anaconda安装的盘&#xff0c;yo…

格蠹汇编阅读理解

一、调试工具使用方式 WinDbg常用命令&#xff1a; 执行 lm 命令&#xff0c;可以看到进程中有几个模块。执行~命令列一下线程。用!heap 命令列一下堆。执行!address 命令可以列出用户态空间中的所有区域。搜索吧&#xff01;就从当前进程用户态空间的较低地址开始搜&#xf…

基于大数据技术Hadoop的气象分析可视化大屏设计和实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

Android - 手势

Android 提供特殊类型的触摸屏事件&#xff0c;例如捏合、双击、滚动、长按和退缩。 这些都被称为手势。 Android 提供了 GestureDetector 类来接收运动事件并告诉我们这些事件是否对应手势。 要使用它&#xff0c;您需要创建一个 GestureDetector 对象&#xff0c;然后使用 Ge…

SpringBoot源码阅读(1)——环境搭建

SpringBoot官网 官网 https://spring.io/projects/spring-boot 代码仓库 github&#xff1a;https://github.com/spring-projects/spring-boot gitee: https://gitee.com/mirrors/spring-boot 下载代码 git clone https://gitee.com/mirrors/spring-boot.git下载的代码中有些…

如何看自己电脑的ip地址?这些方法教你搞定

在数字化时代&#xff0c;网络已经成为我们生活中不可或缺的一部分。对于每一个接入网络的设备来说&#xff0c;IP地址就像是一个独特的身份证&#xff0c;它标识着设备在网络中的位置。对于电脑用户而言&#xff0c;了解如何查看自己电脑的IP地址&#xff0c;不仅有助于我们更…

满足信创环境运行的国产FTP服务器是什么样的?

2018 年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;我国科技尤其是上游核心技术受制于人的现状对我 国经济发展提出了严峻考验。在全球产业从工业经济向数字经济升级的关键时期&#xff0c;中国明确 “数字中国”建设战略&#xff0c; 抢占数字经济产业链制高点。 在…

【Python】已解决:(paddleocr导包报错)ModuleNotFoundError: No module named ‘paddle’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;paddleocr导包报错&#xff09;ModuleNotFoundError: No module named ‘paddle’ 一、分析问题背景 近日&#xff0c;一些使用PaddleOCR库进行文字…