Android.mk文件语法规范及使用

2024-05-05 20:08
文章标签 android 使用 语法 mk 规范

本文主要是介绍Android.mk文件语法规范及使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

from:http://blog.chinaunix.net/uid-29494667-id-4116547.html

1.概述
Android.mk编译文件是用来向Android NDK描述你的C,C++源代码文件的。具体来说:
该文件是GNU Makefile的一小部分,会被编译系统解析一次或更多次的build系统。因此,您应尽量减少您声明的变量,不要认为某些变量在解析过程中不会被定义。
这个文件的语法允许把你的源代码组织成模块,一个模块属下列类型之一:

静态库

共享库

只有共享库将被安装/复制到您的应用软件包。虽然静态库能被用于生成共享库。你可以在每一个Android.mk file中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。

2.样例简析

在描述语法细节之前,咱们来看一个简单的"hello world"的例子,比如,下面的文件:

sources/helloworld/helloworld.c

sources/helloworld/Android.mk

'helloworld.c'是一个JNI共享库,实现返回"hello world"字符串的原生方法。

相应的Android.mk文件会象下面这样:

---------- cut here ------------------

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE:= helloworld

LOCAL_SRC_FILES := helloworld.c

include $(BUILD_SHARED_LIBRARY)

---------- cut here ------------------

好,我们来解释一下这几行代码:

LOCAL_PATH:= $(call my-dir)

一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。

include $( CLEAR_VARS)

CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),

除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。

LOCAL_MODULE := helloworld

LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'foo'的共享库模块,将会生成'libfoo.so'文件。

重要注意事项

如果你把库命名为‘libhelloworld’,编译系统将不会添加任何的lib前缀,也会生成libhelloworld.so,这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。

LOCAL_SRC_FILES := helloworld.c

LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。【注意,默认的C++源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是定义为‘.cxx’,而不是‘cxx’)(当然这一步我们一般不会去改它)】

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY是编译系统提供的变量,指向一个GNU Makefile脚本(应该就是在build/core目录下的shared_library.mk),负责收集自从上次调用'include $(CLEAR_VARS)'以来,定义在LOCAL_XXX变量中的所有信息,并且决定编译什么,如何正确地去做。并根据其规则生成静态库。同理对于静态库。

3.深入分析

下面我们尝试从一个小模块逐步对android build system做一个深入剖析。选择的这个模块名字叫做acp ,源码位于build\tools\acp目录。

后续很多模块的编译都需要使用到acp,根据编译依赖一般会先编译本模块。当然它也需要依赖到其他文件,需要的时候我们再进行阐述。
acp的Android.mk比较简单,去掉的无用代码后,如下面所示:

Makefile代码   收藏代码
  1. LOCAL_PATH:= $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3.   
  4. LOCAL_SRC_FILES := acp.c  
  5.   
  6. LOCAL_STATIC_LIBRARIES := libhost  
  7. LOCAL_C_INCLUDES := build/libs/host/include  
  8. LOCAL_MODULE := acp  
  9. LOCAL_ACP_UNAVAILABLE := true  
  10.   
  11. include $(BUILD_HOST_EXECUTABLE)   

上面的语句大致的意思就是,使用当前路径下的acp.c源码,引用的include和链接的library都是host模块,最终编译生成一个可在当前主机运行的可执行文件,名字为acp(linux环境)。。
这里我们先不谈每一个变量的具体含义和使用,我们先大概看一下一个Android.mk的基本组成。

  1. LOCAL_PATH 定义了当前模块的相对路径,必须出现在所有的编译模块之前
  2. 每个编译模块由include $(CLEAR_VARS) 开始,由include $(BUILD_XXX) 结束
  3. include $(CLEAR_VARS) 是一个编译模块的开始,它会清空除LOCAL_PATH之外的所有LOCA_XXX变量
  4. include $(BUILD_XXX) 描述了编译目标
  5. LOCAL_SRC_FILES 定义了本模块编译使用的源文件,采用的是基于LOCAL_PATH的相对路径
  6. LOCAL_MODULE 定义了本模块的模块名

编译acp还需要了几个可选的变量:

  • LOCAL_STATIC_LIBRARIES 表示编译本模块时需要链接的静态库
  • LOCAL_C_INCLUDES 表示了本模块需要引用的include文件
  • LOCAL_ACP_UNAVAILABLE 表示是否支持acp,如果支持acp,则使用acp进行拷贝,否则使用linux cp拷贝,本模块编译acp,当然是不支持acp了

编译目标

上面我们用到include $(CLEAR_VARS)和include $(BUILD_HOST_EXECUTABLE),那么他们是在哪里定义的呢?除了BUILD_HOST_EXECUTABLE还有哪些BUILD_XXX目标呢?
它们的定义位于build/core/config.mk文件,当然config.mk文件定义的编译目标也很多,下面列举几个常用的目标:
编译目标说明
BUILD_HOST_STATIC_LIBRARY主机上的静态库
BUILD_HOST_SHARED_LIBRARY主机上的动态库
BUILD_HOST_EXECUTABLE主机上的可执行文件
BUILD_STATIC_LIBRARY目标设备上的静态库
BUILD_SHARED_LIBRARY目标设备上的动态库
BUILD_EXECUTABLE目标设备上的可执行文件
BUILD_JAVA_LIBRARYJAVA库
BUILD_STATIC_JAVA_LIBRARY静态JAVA库
BUILD_HOST_JAVA_LIBRARY主机上的JAVA库
BUILD_PACKAGEAPK程序
具体的每一个目标,等我们遇到的时候我们再详细进行讲解,先重点分析 LOCAL_PATH

有人就问了,在本Android.mk中又没有使用到LOCAL_PATH,为什么先 要定义这么一个变量呢?为什么规定必须放在所有的include $(CLEAR_VARS)之前呢?
在Android.mk中我们发现有LOCAL_SRC_FILES := acp.的定义,NDK文件中对LOCAL_SRC_FILES 的说明如下:
This is a list of source files that will be built for your module. Only list the files that will be passed to a compiler, since the build system automatically computes dependencies for you.
Note that  source files names are all relative to LOCAL_PATH and you can use path components .
因此在定义LOCAL_SRC_FILES 时已经间接的使用到了LOCAL_PATH变量,即定义LOCAL_SRC_FILES是用的基于当前路径的相对路径。
我们接着看看为什么LOCAL_PATH的定义必须要放到所有的include $(CLEAR_VARS)之前。
LOCAL_PATH通过调用my-dir函数来获取当前的路径,my-dir函数的定义位于core/definitions.mk文件:
Makefile代码   收藏代码
  1. <span style="font-size: small;"># Figure out where we are.  
  2. define my-dir  
  3. $(strip \  
  4.   $(eval md_file_ := $$(lastword $$(MAKEFILE_LIST))) \  
  5.   $(if $(filter $(CLEAR_VARS),$(md_file_)), \  
  6.     $(error LOCAL_PATH must be set before including $$(CLEAR_VARS)) \  
  7.    , \  
  8.     $(patsubst %/,%,$(dir $(md_file_))) \  
  9.    ) \  
  10. )  
  11. endef  
请注意,这里明确的说明了LOCAL_PATH的定义必须要放在任何include $(CLEAR_VARS)语句之前,如果不这么做的话,编译就直接报错,停止不干了。
可是它是怎么判断LOCAL_PATH的定义是在任何include $(CLEAR_VARS)语句之前呢,我们看到有这么一句话:
Makefile代码   收藏代码
  1. $(if $(filter $(CLEAR_VARS),$(md_file_))  
 这个判断语句是个关键,我们先看看CLEAR_VARS变量的定义
CLEAR_VARS
在build/core/config.mk中有如下明确的定义:
Makefile代码   收藏代码
  1. CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk  
而BUILD_SYSTEM的定义在build/core/main.mk文件中:
BUILD_SYSTEM := $(TOPDIR)build/core
Makefile代码   收藏代码
  1. BUILD_SYSTEM := $(TOPDIR)build/core  
紧接着:
Makefile代码   收藏代码
  1. TOPDIR :=  
即TOPDIR为android源码的根目录, BUILD_SYSTEM= build/core, 所以CLEAR_VARS变量的值就为build/core/clear_vars.mk,当然这个也是相对于Android源码根路径。
得到了CLEAR_VARS变量的值,我们再回到my-dir函数中。
my-dir
根据gnu make定义,gnu make 会自动将所有读取的makefile路径都会加入到MAKEFILE_LIST变量中,而且是按照读取的先后顺序添加。
那么,在运行本makefile文件时,$(MAKEFILE_LIST)字符串中最后一个makefile肯定是最后读取的makefile,即$(lastword $(MAKEFILE_LIST))则会返回build/tools/acp/Android.mk,此字符串经过$(eval md_file_ := $$(lastword $$(MAKEFILE_LIST))运算,赋值给了临时变量md_file_。
判断md_file_中是否包含CLEAR_VARS变量的值$(if $(filter $(CLEAR_VARS),$(md_file_)),肯定也就会返回失败,再通过$(patsubst %/,%,$(dir $(md_file_)))函数,则会得到当前路径,即build/tools/acp
如果我们在include $(CLEAR_VARS)之后,再调用my-dir函数,那么$$(lastword $$(MAKEFILE_LIST))肯定就会返回$(BUILD_SYSTEM)/clear_vars.mk,同时,$(patsubst %/,%,$(dir $(md_file_))) 也就会返回$(BUILD_SYSTEM)的值build/core,而不是当前的路径build/tools/acp。
这么一来得到的LOCAL_PATH的值就是错误的值,依赖LOCAL_PATH的其他变量也就更加不可能是正确的了!所以说 ,LOCAL_PATH必须要在任何including $(CLEAR_VARS))之前定义 。

讨论完LOCAL_PATH,我们紧接着来看看LOCAL_SRC_FILES。
LOCAL_SRC_FILES:
Makefile代码   收藏代码
  1. LOCAL_SRC_FILES := acp.c  
LOCAL_SRC_FILES变量的意思见名知意,很明显是用来记录当前模块的源文件列表的一个变量。

这里是他的赋值,我们下面来看看他的使用的地方。在build/core/binary.mk中有如下的部分:
Makefile代码   收藏代码
  1. ###########################################################  
  2. ## C: Compile .c files to .o.  
  3. ###########################################################  
  4.   
  5. c_arm_sources    := $(patsubst %.c.arm,%.c,$(filter %.c.arm,$(LOCAL_SRC_FILES)))  
  6. c_arm_objects    := $(addprefix $(intermediates)/,$(c_arm_sources:.c=.o))  
  7.   
  8. c_normal_sources := $(filter %.c,$(LOCAL_SRC_FILES))  
  9. c_normal_objects := $(addprefix $(intermediates)/,$(c_normal_sources:.c=.o))  
  10.   
  11. $(c_arm_objects):    PRIVATE_ARM_MODE := $(arm_objects_mode)  
  12. $(c_arm_objects):    PRIVATE_ARM_CFLAGS := $(arm_objects_cflags)  
  13. $(c_normal_objects): PRIVATE_ARM_MODE := $(normal_objects_mode)  
  14. $(c_normal_objects): PRIVATE_ARM_CFLAGS := $(normal_objects_cflags)  
  15.   
  16. c_objects        := $(c_arm_objects) $(c_normal_objects)  
  17.   
  18. ifneq ($(strip $(c_objects)),)  
  19. $(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c $(yacc_cpps) $(proto_generated_headers) $(LOCAL_ADDITIONAL_DEPENDENCIES)  
  20.      $(transform-$(PRIVATE_HOST)c-to-o)  
  21. -include $(c_objects:%.o=%.P)  
  22. endif  
 
分析上面的代码,
1. 我们首先从LOCAL_SRC_FILES中得到所有的C文件
c_normal_sources value acp.c
2. 定义一个变量,c_normal_objects,用来表示生成的.o文件的路径
c_normal_objects value out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
其中,$(c_normal_sources:.c=.o)会返回acp.o,那么c_normal_objects的关键就是$(intermediates)变量

intermediates
通过查找,我们可以发现在以下地方有intermediates的赋值:
Makefile代码   收藏代码
  1. build/core/host_java_library.mk:intermediates := $(call local-intermediates-dir)  
  2. build/core/base_rules.mk:intermediates := $(call local-intermediates-dir)  
  3. build/core/dynamic_binary.mk:guessed_intermediates := $(call local-intermediates-dir)  
  4. build/core/java.mk:intermediates := $(call local-intermediates-dir)  
即,他们的都是调用local-intermediates-dir函数获取当前的intermediates的值

在build/core/definitions.mk中有local-intermediates-dir函数的定义:
Makefile代码   收藏代码
  1. # Uses LOCAL_MODULE_CLASS, LOCAL_MODULE, and LOCAL_IS_HOST_MODULE  
  2. # to determine the intermediates directory.  
  3. #  
  4. # $(1): if non-empty, force the intermediates to be COMMON  
  5. define local-intermediates-dir  
  6. $(strip \  
  7.     $(if $(strip $(LOCAL_MODULE_CLASS)),, \  
  8.         $(error $(LOCAL_PATH): LOCAL_MODULE_CLASS not defined before call to local-intermediates-dir)) \  
  9.     $(if $(strip $(LOCAL_MODULE)),, \  
  10.         $(error $(LOCAL_PATH): LOCAL_MODULE not defined before call to local-intermediates-dir)) \  
  11.     $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),$(LOCAL_MODULE),$(LOCAL_IS_HOST_MODULE),$(1)) \  
  12. )  
  13. endef  
根据注释,我们知道 local-intermediates-dir的定义会依赖于LOCAL_MODULE_CLASS, LOCAL_MODULE, 和 LOCAL_IS_HOST_MODULE这三个变量的值。

我们先通过添加打印的方式,得到如下三个变量的值:
LOCAL_MODULE_CLASS value EXECUTABLES
LOCAL_MODULE value acp
LOCAL_IS_HOST_MODULE value true
其中,LOCAL_MODULE 的值是acp,这个肯定的是没问题的,因为在acp模块的Android.mk文件中有明确的定义:LOCAL_MODULE := acp
那LOCAL_MODULE_CLASS 为什么是EXECUTABLES呢?我们这里先猜一下,估计是和最后一句include $(BUILD_HOST_EXECUTABLE )有关,
同样,LOCAL_IS_HOST_MODULE 为true,估计也是和include $(BUILD_HOST _EXECUTABLE)有关了。

我们还是先回到 local-intermediates-dir函数中,其中前两个判断是判断是否定义LOCAL_MODULE_CLASS和LOCAL_MODULE,如果未空,则直接报错,这里也就说明了在每一个编译模块中(即include $(CLEAR_VARS)和include $(BUILD_XXX)之间)必须定义LOCAL_MODULE的值。

因此,如果LOCAL_MODULE_CLASS和LOCAL_MODULE都不为空时,则执行intermediates-dir-for这个函数,$(LOCAL_MODULE_CLASS),$(LOCAL_MODULE),$(LOCAL_IS_HOST_MODULE)还有 local-intermediates-dir函数的第一个参数 $(1)会作为参数传给intermediates-dir-for函数。根据上面intermediates的赋值部分的代码,我们知道调用local-intermediates-dir函数时没有传递任何参数,因此此时的$(1)即为空,传给intermediates-dir-for函数的也就只有上述的3个参数。

intermediates-dir-for
下面我们来看intermediates-dir-for
Makefile代码   收藏代码
  1. ###########################################################  
  2. ## The intermediates directory.  Where object files go for  
  3. ## a given target.  We could technically get away without  
  4. ## the "_intermediates" suffix on the directory, but it's  
  5. ## nice to be able to grep for that string to find out if  
  6. ## anyone's abusing the system.  
  7. ###########################################################  
  8.   
  9. # $(1): target class, like "APPS"  
  10. # $(2): target name, like "NotePad"  
  11. # $(3): if non-empty, this is a HOST target.  
  12. # $(4): if non-empty, force the intermediates to be COMMON  
  13. define intermediates-dir-for  
  14. $(strip \  
  15.     $(eval _idfClass := $(strip $(1))) \  
  16.     $(if $(_idfClass),, \  
  17.         $(error $(LOCAL_PATH): Class not defined in call to intermediates-dir-for)) \  
  18.     $(eval _idfName := $(strip $(2))) \  
  19.     $(if $(_idfName),, \  
  20.         $(error $(LOCAL_PATH): Name not defined in call to intermediates-dir-for)) \  
  21.     $(eval _idfPrefix := $(if $(strip $(3)),HOST,TARGET)) \  
  22.     $(if $(filter $(_idfPrefix)-$(_idfClass),$(COMMON_MODULE_CLASSES))$(4), \  
  23.         $(eval _idfIntBase := $($(_idfPrefix)_OUT_COMMON_INTERMEDIATES)) \  
  24.       , \  
  25.         $(eval _idfIntBase := $($(_idfPrefix)_OUT_INTERMEDIATES)) \  
  26.      ) \  
  27.     $(_idfIntBase)/$(_idfClass)/$(_idfName)_intermediates \  
  28. )  
  29. endef  
根据注释,我们可以知道$(1),$(2),$(3),$(4)这4个参数的意义。
而以下三行则是使用$(1),$(2),$(3)来定义三个临时变量:_idfClass,_idfName 和_idfPrefix 
$(eval _idfClass := $(strip $(1))) 
$(eval _idfName := $(strip $(2)))
$(eval _idfPrefix := $(if $(strip $(3)),HOST,TARGET))
在本例中这三个临时变量的值则为:EXECUTABLES、acp和HOST

下面关键的一个临时变量是_idfIntBase ,我们发现无论$(if $(filter $(_idfPrefix)-$(_idfClass),$(COMMON_MODULE_CLASSES))$(4)是真还是假,
_idfIntBase 的值不是$(HOST_OUT_COMMON_INTERMEDIATES)就是$(HOST_OUT_INTERMEDIATES),因为上面调用时$(4)为空,在这里估计判断结果 应该为假,即应该执行
Makefile代码   收藏代码
  1. $(eval _idfIntBase := $($(_idfPrefix)_OUT_INTERMEDIATES))  

下面我就看看这个$(HOST_OUT_INTERMEDIATES )到底是一个什么变量。
在build/core/envsetup.mk中有明确的定义:
Makefile代码   收藏代码
  1. HOST_OUT_INTERMEDIATES := $(HOST_OUT)/obj   
HOST_OUT也在本文件中定义:
Makefile代码   收藏代码
  1. HOST_OUT := $(HOST_OUT_$(HOST_BUILD_TYPE))   
HOST_BUILD_TYPE默认值为release:
Makefile代码   收藏代码
  1. # the host build defaults to release, and it must be release or debug  
  2. ifeq ($(HOST_BUILD_TYPE),)  
  3. HOST_BUILD_TYPE := release  
  4. endif   
因此,
Makefile代码   收藏代码
  1. HOST_OUT := $(HOST_OUT_release)  
而HOST_OUT_release的定义如下:
Makefile代码   收藏代码
  1. HOST_OUT_release := $(HOST_OUT_ROOT_release)/$(HOST_OS)-$(HOST_ARCH)  
HOST_OUT_ROOT_release的定义:
Makefile代码   收藏代码
  1. HOST_OUT_ROOT_release := $(OUT_DIR)/host   
在Linux上编译,因此HOST_OS := linux ,而我们的机器采用的是Intel Xeon X3440 CPU,即x86架构,因此HOST_ARCH:= x86

经过上述分析,
$(HOST_OUT_INTERMEDIATES ) =out/host/linux-x86/obj
intermediates-dir-for 函数返回out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
local-intermediates-dir 函数返回out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
intermediates 变量的值为out/host/linux-x86/ob/EXECUTABLES/acp_intermediates
c_normal_objects 变量的值为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o

c_objects
下面我们回到LOCAL_SRC_FILES的编译部分
Makefile代码   收藏代码
  1.  c_objects        := $(c_arm_objects) $(c_normal_objects)  
  2.   
  3. ifneq ($(strip $(c_objects)),)  
  4. $(c_objects): $(intermediates)/%.o: $(TOPDIR)$(LOCAL_PATH)/%.c $(yacc_cpps) $(proto_generated_headers) $(LOCAL_ADDITIONAL_DEPENDENCIES)  
  5.      $(transform-$(PRIVATE_HOST)c-to-o)  
  6. -include $(c_objects:%.o=%.P)  
  7. endif   
其中c_arm_objects为空,c_normal_objects 的值为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o
所以c_objects 的值也为out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o

执行c_objects目标时,依赖$(intermediates)/%.o、 $(TOPDIR)$(LOCAL_PATH)/%.c、$(yacc_cpps) 、$(proto_generated_headers)和$(LOCAL_ADDITIONAL_DEPENDENCIES)
执行如下操作: $(transform-$(PRIVATE_HOST)c-to-o)


这篇关于Android.mk文件语法规范及使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/962568

相关文章

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.

Python中help()和dir()函数的使用

《Python中help()和dir()函数的使用》我们经常需要查看某个对象(如模块、类、函数等)的属性和方法,Python提供了两个内置函数help()和dir(),它们可以帮助我们快速了解代... 目录1. 引言2. help() 函数2.1 作用2.2 使用方法2.3 示例(1) 查看内置函数的帮助(

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

LiteFlow轻量级工作流引擎使用示例详解

《LiteFlow轻量级工作流引擎使用示例详解》:本文主要介绍LiteFlow是一个灵活、简洁且轻量的工作流引擎,适合用于中小型项目和微服务架构中的流程编排,本文给大家介绍LiteFlow轻量级工... 目录1. LiteFlow 主要特点2. 工作流定义方式3. LiteFlow 流程示例4. LiteF

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

使用jenv工具管理多个JDK版本的方法步骤

《使用jenv工具管理多个JDK版本的方法步骤》jenv是一个开源的Java环境管理工具,旨在帮助开发者在同一台机器上轻松管理和切换多个Java版本,:本文主要介绍使用jenv工具管理多个JD... 目录一、jenv到底是干啥的?二、jenv的核心功能(一)管理多个Java版本(二)支持插件扩展(三)环境隔

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用