Android.mk语法详解
前言
目前 Android 工程可以通过 .mk
、或者 .cmake
的形式构建 NDK 工程,较新的 Android 工程一般采用的是 .cmake
构建 NDK 源码,而相对创建时间久的工程则大多数采用的是 .mk
的形式构建。下文主要是通过解析 Android 源码里的 docs
文档来深入了解 .mk
语法,其中会对原文里面一些描述通过实际工程加以描述。(原文以及演示的工程的链接将会在文章末尾给出)
MK 语法概述
一个 Android.mk
文件是用来描述 Android 的工程源码如何被构建系统所构建。进一步来说:
Android.mk 文件是一种会被构建系统解析一次或多次以上的
GUN Makefile
片段。.mk
语法允许构建工程的module
源文件,每个module
可以在Android.mk
中被声明为下面的其中一种库:- 静态库(a static library)
- 动态库(a shared library)
只有动态库会被拷贝进项目工程,而静态库则是产生动态库的中间产物。在项目工程中,可以定义一个或多个
module
在Android.mk
文件中,或者可以使用同一份源码(.c
/.cpp
)在多个module
内。构建系统会自动地为
.mk
工程处理细节问题。例如,我们不需要在Android.mk
文件中列出源码的头文件,或者定义生成库的需要使用到的中间文件,NDK 构建工程会自动地为我们完成这些细节任务。同时新版本的 NDKtoolchain
/platform
支持向下兼容Android.mk
语法。
NDK工程
对上图的解析说明:
src
文件中包含着Java
源文件、jni
目录。jni
目录下包含着.cpp
/.mk
;jni/Android.mk
描述如何把hello_mk.cpp
等文件构建成一个动态库,其内容如下:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)LOCAL_MODULE := hello_mk LOCAL_SRC_FILES := hello_mk.cppinclude $(BUILD_SHARED_LIBRARY)
在工程
app
下的build.gradle
文件下对Android.mk
/Appplication.mk
文件位置的声明一般有两种方式:
方式一:android {...task ndkBuild(type:Exec,description:'NDK Project'){commandLine "C:\\Users\\iroot\\AppData\\Local\\Android\\Sdk\\ndk\\16.1.4479499\\ndk-build.cmd",//配置ndk的路径'NDK_PROJECT_PATH=build/intermediates/ndk', // ndk默认的生成so的文件路径'NDK_LIBS_OUT=src/main/jniLibs', // 配置的我们想要生成的so文件所在的位置'APP_BUILD_SCRIPT=src/main/jni/Android.mk', // 指定项目的 Android.mk 所在位置'NDK_APPLOCATION_MK=src/main/jni/Application.mk' // 指定项目的 Applicaiton.mk 所在位置}tasks.withType(JavaCompile){ //使用ndkBuildcompileTask ->compileTask.dependsOn ndkBuild} }
方式二:(方式二需要工程的
setting.gradle
文件配合声明 NDK 的所在位置)android {...defaultConfig {...externalNativeBuild {ndkBuild {arguments 'NDK_APPLICATION_MK:=src/main/jni/Application.mk'cFlags ''cppFlags ''abiFilters 'arm64-v8a, armeabi-v7a, x86, x86_64'}}}externalNativeBuild {ndkBuild {path 'src/main/jni/Android.mk'}} }
MK 语法详解(一)
LOCAL_PATH := $(call my-dir)
解析:每个 Android.mk
文件必须在文件头部最开始处定义 LOCAL_PATH
变量,该变量用来获取工程中的文件节点。在上述工程图中,通过构建系统提供的函数 my-dir
获取 Android.mk
当前的所在的目录节点。
include $(CLEAR_VARS)
解析:CLEAR_VARS
变量是构建系统提供,同时指向一个特殊的 GNU Makefile
,主要是用来清除如 LOCAL_XXX
所定义的变量(e.g. LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, etc...
)、以及 LOCAL_PATH
环境中的异常(CLEAR_VARS
可简单理解为初始化环境)。include $(CLEAR_VARS)
声明是必须的,因为 Android.mk
在第一次被解析的时候,变量被初始化为为知的值(这里个人理解为类似 C
中的数据定义未初始化,被其值被系统赋值为垃圾值)。
LOCAL_MODULE := hello_mk
解析:LOCAL_MODULE
变量是用来声明需要被生成的 module
名称。该定义的名称在整个工程中必须是唯一的,同时在构建的时候,系统会自动为该 module
名称补全前缀、以及后缀。也就是说,上文定义的动态库名称 hello_mk
最后被补全为 libhello_mk.so
。当然,如果我们使用 LOCAL_MODULE := libhello_mk
声明的时候,系统则不会为其添加前缀。
LOCAL_SRC_FILES := hello-jni.c
解析:变量 LOCAL_SRC_FILES
是用来定义将要生成的目标动态库所需要的源码文件列表,如 c
或者 c++
文件。但我们不要把头文件或者被 include
的文件也定义到该变量列表中,因为这些构建系统已经自动地帮我们完成这些任务。
include $(BUILD_SHARED_LIBRARY)
解析:变量 BUILD_SHARED_LIBRARY
被定义后,GNU Makefile
脚本就会负责把 include $(BUILD_SHARED_LIBRARY)
往上定义的如 LOCAL_XXX
变量都收集起来,直到离 include $(BUILD_SHARED_LIBRARY)
最近的定义的 include $(CLEAR_VARS)
为止。可简单理解为 [include $(CLEAR_VARS) ... include $(BUILD_SHARED_LIBRARY)]
之间的 LOCAL_XXX
的变量将决定如何生成一个动态库。当然,BUILD_STATIC_LIBRARY
变量是用来声明定义生成静态库。
自定义变量
构建系统会提供一系列的 .mk
变量供我们使用,当然我们也可以在我们工程需要的时候自定义某些变量。但需要注意不能与构建系统保留的变量名发生冲突:
- 以
LOCAL_
开头的变量名;(如LOCAL_MODULE
) - 以
PRIVATE_
、NDK_
、APP_
开头的变量名;(这些开头的变量名称被用以系统内部) lower-case
名称;(用以系统内部,如my-dir
)
如果我们需要自定义变量,官方推荐我们使用 MY_
前缀开头的变量,避免与系统变量发生冲突。例子如下:
MY_SOURCES := foo.c
ifneq ($(MY_CONFIG_BAR),)
MY_SOURCES += bar.c
endif
LOCAL_SRC_FILES += $(MY_SOURCES)
MK 语法详解(二)
在上文我们了解了 CLEAR_VARS
、BUILD_SHARED_LIBRARY
、BUILD_STATIC_LIBRARY
这几个变量。构建系统还提供了其他种类的变量供我们在 mk
文件中使用,下面我们来一一认识了解它们吧。
NDK提供的变量
PREBUILT_SHARED_LIBRARY
该变量用来指定一个需要被依赖进工程的动态库。与 BUILD_SHARED_LIBRARY
、BUILD_STATIC_LIBRARY
不同的是,该变量对应的 LOCAL_SRC_FILES
必须被初始化为一个需要被纳入到工程的动态库路径,而不是源码文件。NDK Prebuilt library support 参考资料
使用方式 (这里不做解析,感兴趣可以查看提供的 NDK Prebuilt library support 参考资料
):
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include #导出libfoo.so的头文件
include $(PREBUILT_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := foo-user
LOCAL_SRC_FILES := foo-user.c
LOCAL_SHARED_LIBRARY := foo-prebuilt
include $(BUILD_SHARED_LIBRARY)
PREBUILT_STATIC_LIBRARY
作用和 PREBUILT_SHARED_LIBRARY
类似,只是该初始化的为静态库路径。
TARGET_ARCH
指定程序运行的目标 CPU 架构指令集的名称;
TARGET_PLATFORM
指定 Android.mk
文件将被哪一个 Android 版本解析。例如,'android-3'
对应于 Android 1.5 系统镜像。
TARGET_ARCH_ABI
目标 CPU+ABI 被 Android.mk
解析。
部分举例:
armeabi
=> when targetting ARMv5TE or higher CPUs
armeabi-v7a
=> when targetting ARMv7 or higher CPUs
x86
=> when targetting x86 CPUs
同时 armeabi-v7a
系统可以兼容 armeabi
二进制文件。
代码例子:
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libfoo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
同时假设上述代码目录结构如下:
Android.mk --> the file above
armeabi/libfoo.so --> the armeabi prebuilt shared library
armeabi-v7a/libfoo.so --> the armeabi-v7a prebuilt shared library
include/foo.h --> the exported header file
TARGET_ABI
与 TARGET_PLATFORM
、TARGET_ARCH_ABI
变量类似,变量 TARGET_ABI
在我们需要测试某一块真机的系统镜像的时候会非常有用。默认值为 android-3-armeabi
。(升级 Android NDK 1.6_r1,将会默认使用 android-3-arm
)
函数宏
接下来将要描述的是 GUN Make 函数宏(function macros
),函数宏返回的是文本信息(可以理解为字符串),使用的格式如:$(call <function>)
。
my-dir
在上文我们提到构建系统提供的函数 my-dir
:返回最近导入的 Makefile
的路径,在 NDK 中一般为 Android.mk 当前的目录节点路径。这里我们再展开解析该函数在使用中需要注意的点。
$(call my-dir)
变量获取的是最新导入的 Makefile
路径,也就意味着,当 include
新的文件路径进来以后,$(call my-dir)
返回的是该新的文件路径。如下源码一:
源码一
LOCAL_PATH := $(call my-dir)
... declare one module
include $(LOCAL_PATH)/foo/Android.mk
LOCAL_PATH := $(call my-dir)
... declare another module
例子一存在二次调用 $(call my-dir)
,第二次调用返回的将是 $(LOCAL_PATH)/foo
而不是 $PATH
。因此,比较好的写法做法是把将要新增的 Android.mk
文件放在文件末尾,如下源码二:
源码二
LOCAL_PATH := $(call my-dir)
... declare one module
LOCAL_PATH := $(call my-dir)
... declare another module
# extra includes at the end of the Android.mk
include $(LOCAL_PATH)/foo/Android.mk
但源码二给出形式还不是最好的,如当最后需要 include
多个 Android.mk
呢?最好的写法是把第一次获取到的 $(call my-dir)
获取到的值预先保存在另一个变量:
源码三
MY_LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(MY_LOCAL_PATH)
... declare one module
include $(LOCAL_PATH)/foo/Android.mk
LOCAL_PATH := $(MY_LOCAL_PATH)
... declare another module
all-subdir-makefiles
该变量返回当前包含 Android.mk
的 my-dir
路径,以及子目录中含有 Android.mk
的路径。例如:
sources/foo/Android.mk
sources/foo/lib1/Android.mk
sources/foo/lib2/Android.mk
如果在 sources/foo/Android.mk
文件中含有 include $(call all-subdir-makefiles)
,则将会自动地 include
文件 sources/foo/lib1/Android.mk
和 sources/foo/lib2/Android.mk
。默认地,NDK 只会遍历查找 sources/*/Android.mk
格式地目录下的 Android.mk
。
this-makefile
返回当前的 Makefile
文件所在文件夹的路径。
parent-makefile
返回当前 Makefile
文件位于相对文件树节点的父 Makefile
路径。
grand-parent-makefile
…
import-module
该函数允许我们通过 module
的名字导入另一个 module
,同时自动地导入该 module
的 Android.mk
文件。但该 module
需要在 NDK_MODULE_PATH
该变量中声明。代码如下例子:
$(call import-module,<name>)
Module描述变量
接下来解析的变量是用来描述我们的 module
将要如何被构建系统所构建。
LOCAL_PATH
该变量被赋值为当前文件的路径。我们必须把它定义在 Android.mk
文件中开头的地方。同时该变量是不会被 $(CLEAR_VARS)
函数所清除,所以我们需要为每个需要的 Android.mk
定义路径(如在单一个 Android.mk
文件中导入多个 module
)。写法如下:
LOCAL_PATH := $(call my-dir)
LOCAL_MODULE
用来声明我们 module
的名称。声明的 module
的名称必须在该工程内是唯一的,该名称不可以包含任何的空格,同时需要声明在任何的 $(BUILD_XXXX)
变量前。该 module
的名称默认是生成文件的名称,如 module
的名称为 foo
,则生成的静态库的文件名为 libfoo.a
,或者生成静态库的文件名为 linfoo.so
。
LOCAL_MODULE_FILENAME
这是个可选的变量,用来重载 LOCAL_MODULE
定义的名称。写法如下:
LOCAL_MODULE := foo-version-1
LOCAL_MODULE_FILENAME := libfoo
需要注意的是,我们不可以把路径名或者文件的后缀名称定义在 LOCAL_MODULE_FILENAME
。
LOCAL_SRC_FILES
该变量用来声明构建生成目标文件(静态 / 动态 / 可执行文件)所需要的源文件(C
/ C++
),只有定义在该变量的源文件才会被编译进目标文件,同时构建系统会自动地为源码文件处理头文件导入这些细节操作。
因为源码文件的路径已经在声明 LOCAL_PATH
的时候已经导入进环境,因此我们只需要补充源码文件具体的目录位置即可。写法如下:
LOCAL_SRC_FILES := foo.c \toto/bar.c
LOCAL_CPP_EXTENSION
该变量用来声明 C++
的文件扩展后缀名称。我们可以更改默认的后缀(.cpp
)名称声明。例子如下:
LOCAL_CPP_EXTENSION := .cxx
LOCAL_C_INCLUDES
相对于 NDK 的根目录路径,该变量定义编译源码时会被追加到导入搜索路径。如下例子:
LOCAL_C_INCLUDES := sources/foo
或者:
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
LOCAL_C_INCLUDES
生效的时机是在 LOCAL_CFLAGS
/ LOCAL_CPPFLAGS
变量之前。同时,LOCAL_C_INCLUDES
路径在进行 native 层的调试的时候会被使用到。
LOCAL_CFLAGS
该变量声明的值会在构建 C
或 C++
源码文件的时候给编译器设置编译参数,是一个可选的变量。这对于指定宏定义或者编译选项是非常有帮助的。
同时官方推荐不要尝试去调整优化选项、或者调试等级在我们的 Android.mk
文件,这些会通过 Application.mk
指定相关的信息为我们自动地处理,同时让 NDK 在调试的时候为我们生成有用的调试信息。
在 android-ndk-1.5_r1
的 NDK 版本,相对应的 LOCAL_CFLAGS
定义只针对 C
文件起效,而 C++
则需要通过设置 LOCAL_CPPFLAGS
变量指定。
通过 LOCAL_CFLAGS
也可以像 LOCAL_C_INCLUDES
指定导入的源码文件路径,但推荐使用 LOCAL_C_INCLUDES
变量,因为后者会在 native 层调试的时候也需要使用到。
LOCAL_CXXFLAGS
LOCAL_CXXFLAGS
变量是 LOCAL_CPPFLAGS
的别名。但该变量可能在新的 NDK 版本中不再被支持。
LOCAL_CPPFLAGS
该变量会被拼接在 LOCAL_CFLAGS
变量之后,同时只对 C++
源文件生效。
LOCAL_STATIC_LIBRARIES
LOCAL_STATIC_LIBRARIES
变量将被被链接进 module
里面的静态库列表,而这些静态库是通过 BUILD_STATIC_LIBRARY
定义构建的。
LOCAL_SHARED_LIBRARIES
作用与 LOCAL_STATIC_LIBRARIES
相同,只是定义的是动态库列表。
LOCAL_LDLIBS
LOCAL_LDLIBS
用于指定系统库通过 -l
前缀,指定的系统库将在编译的时候加载进我们目标 module
。例如:
LOCAL_LDLIBS := -lz
通过指定 -lz
参数,编译器会加载 /system/lib/libz.so
进我们目标生成的 module
。(参考 docs/STABLE-APIS.html
)
LOCAL_ALLOW_UNDEFINED_SYMBOLS
在编译生成动态库的时候,构建系统回去检测源码中是否存在 undefined symbol
的错误,这一行为有利于帮助我们提前发现代码中的 Bug
。但实际中,我们总会遇到某些原因让我们不得不关闭该检测行为,当 LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
会关闭对 undefined symbol
的检查。但带来的风险是在加载动态库时会使得程序发生崩溃。
LOCAL_ARM_MODE
默认条件下,ARM 目标二进制文件将会在 thumb
模式下生成,同时在该模式下生成的每条指令都是 16 位的。我们可以通过该变量指定在 arm
模式下生成我们的目标文件,也就是说此时的目标文件的指令宽是 32 位的。写法如下:
LOCAL_ARM_MODE := arm
在生成指定的目标文件的过程中,我们还可以通过指定某个源码文件按照我们需要的方式构建,如:
LOCAL_SRC_FILES := foo.c bar.c.arm
通过在源码文件名称后面后添加 .arm
可以指定在编译的时候,该文件以 arm
模式构建。
同时,在 Application.mk
文件通过 APP_OPTIM := debug
定义的方式同样可以生成 ARM 目标二进制文件。在官方文档中指出这是因为由于工具链调试器在处理 thumb
指令有 bug
。
LOCAL_ARM_NEON
当该变量被设为 true
的时允许我们使用 ARM Advanced SIMD
(又名 NEON)、以及 GCC 内敛函数在我们的 C
、C++
代码中、同时允许 NEON 汇编出现在汇编文件中。
我们只允许指定 ARMv7 汇编指令集在对应架构 armeabi-v7a ABI
中。但并非所有的基于 ARMv7 架构的 CPU 都支持 NEON 扩展指令集,这需要我们执行运行检测才可以发现是否 NEON 指令可以安全运行。如 LOCAL_ARM_MODE
可以在运行时指定特定文件可以支持 arm
模式进行编译,LOCAL_ARM_NEON
指令同样也可以通过指定某特定的文件追加 .neon
后缀,来支持 NEON
扩展指令集。写法如下:
LOCAL_SRC_FILES = foo.c.neon \bar.c \zoo.c.arm.neon
在上面的例子中,foo.c
文件将以 thumb+neon
模式编译、bar.c
文件将以 thumb
模式编译、zoo.c
将以 arm + neon
模式编译。需要注意的是,如果某个文件同时需要以 arm
、neon
模式编译,那么 .neon
后缀必须跟在 .arm
后面。
LOCAL_DISABLE_NO_EXECUTE
Android NDK r4
版本新增对 NX bit
的安全特性的支持。该特性被默认支持,我们可以通过该变量设置 LOCAL_DISABLE_NO_EXECUTE := true
来关闭该特性。
关于 NX
特性可以参考:
- http://en.wikipedia.org/wiki/NX_bit
- http://www.gentoo.org/proj/en/hardened/gnu-stack.xml
LOCAL_EXPORT_CFLAGS
LOCAL_EXPORT_CFLAGS
定义了一组 C
/ C++
编译器参数,当其他模块以LOCAL_STATIC_LIBRARIES
/ LOCAL_SHARED_LIBRARIES
方式引用该模块时,就会将该组值加入到 LOCAL_CFLAGS
,从而传递给编译器。如下例子:
代码片段一:
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_CFLAGS := -DFOO=1
include $(BUILD_STATIC_LIBRARY)
代码片段二:
include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_CFLAGS := -DBAR=2
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)
在代码片段一种,我们通过 foo/foo.c
构建目标静态库 foo
,同时 LOCAL_EXPORT_CFLAGS := -DFOO=1
。在代码片段二中,我们通过 bar.c
构建动态库 bar
,这时候 -DFOO=1 -DBAR=2
会传递给编译器以用来构建动态库 bar
。
LOCAL_EXPORT_CFLAGS
定义的 flags
是可以被继承的。假设 zoo
依赖 bar
,而 bar
依赖 foo
,那么 zoo
也会继承来自 foo
中导出的 flags
。在上述代码片段中,定义的 LOCAL_EXPORT_CFLAGS
对于构建本模块是不生效的,如上述例子中,构建 foo
时声明的 LOCAL_EXPORT_CFLAGS := -DFOO=1
不会传给编译器。
LOCAL_EXPORT_CPPFLAGS
作用和 LOCAL_EXPORT_CFLAGS
相同,但作用于 C++
文件。
LOCAL_EXPORT_C_INCLUDES
作用和 LOCAL_EXPORT_CFLAGS
相同,但只是针对 C
导入的路径。当 bar.c
需要 foo
模块提供的头文件的时候,该定义会很有帮助。
LOCAL_EXPORT_LDLIBS
和 LOCAL_EXPORT_CFLAGS
相同,但针对的是链接器 flags
。由于 Unix 的链接器工作的方式,导入的链接器 flags
将会被追加到我们 module
的 LOCAL_LDLIBS
变量处。
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)
在上述例子中,foo
是静态库,并且依赖系统库。同时 LOCAL_EXPORT_LDLIBS
定义用于导出依赖。当在编译器构建 bar
的时候,将会把 -llog
(表示依赖系统的 日志库)构建进动态库中。
参考资料
- Android.mk file syntax specification
- Application.mk file syntax specification
后言
Android 原生开发随着 Flutter 的到来,前景开始变得不那么明朗。Android 原生可以实现一般功能,在 Flutter 同样也可以实现,同时 Flutter 一份代码可以实现跨平台运行。可能 Flutter 并不是最终跨平台的最终实现方案,但在一定程度上也给 Android 原生开发带来不小的冲击。
但随着目前 5G 时代的来临、以及人们普遍开始越来越重视应用安全,如应用隐私安全等。音视频领域、移动安全领域方兴未艾,尤其移动安全领域比较难找到相关的参考学习资料、学习路线,而笔者正深耘 Android 移动安全领域,希望在这学习 Android 移动安全的路上有你一起长风破浪。
Android.mk语法详解相关推荐
- Android.mk文件详解介绍
Android.mk 编译文件是用来向 Android NDK描述你的 C,C++源代码文件的, 这篇文档描述了它的语法.在阅读下面的内容之前,假定你已经阅读了 docs/OVERVIEW.TXT ...
- android编译系统Android.mk使用详解
2019独角兽企业重金招聘Python工程师标准>>> (1)Android.mk文件首先需要指定LOCAL_PATH变量,用于查找源文件.由于一般情况下 Android.mk和需要 ...
- Android.mk用法详解
Android.mk是Android提供的一个makefile文件,可以将源文件分组为模块.用来引用的头文件目录.需要编译的*.c/.cpp文件.jni源文件.指定编译生成.so共享库文件或者*.a静 ...
- android常用头文件,Android.mk 使用详解
和你一起终身学习,这里是程序员Android 经典好文推荐,通过阅读本文,您将收获以下知识点:一.Android.mk 简介二.Android.mk 的基本格式三.Android.mk 深入学习一四. ...
- Android.mk 文件语法详解
0. Android.mk简介: Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build Syst ...
- Android Framework系统服务详解
Android Framework系统服务详解 操作环境 系统:Linux (Ubuntu 12.04) 平台:高通 Android版本:5.1 PS: 符号...为省略N条代码 一.大致原理分析 A ...
- Android Studio 插件开发详解一:入门练手
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112003 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...
- 《Android游戏开发详解》一3.1 构造方法
本节书摘来异步社区<Android游戏开发详解>一书中的第3章,第3.1节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区 ...
- 《Java和Android开发实战详解》——1.2节Java基础知识
本节书摘来自异步社区<Java和Android开发实战详解>一书中的第1章,第1.2节Java基础知识,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...
最新文章
- Hadoop系列之六:分布式文件系统HDFS
- python3.8.1安装教程-记一次Python3.8.1安装
- 【存储知识学习】第三章磁盘原理与技术3.2磁盘的通俗演绎和3.3磁盘相关高层技术--《大话存储》阅读笔记
- 智能仪表参数设定c语言,智能仪表控制系统:.doc
- oracle exp执行失败,Oracle EXP-EXP-00091的错误原因及处理方法
- hadoop自定义key,value
- E:Modular Stability(组合数)
- netbeans 定制代码_将NetBeans代码模板弯曲到我的意愿
- 本科生手握十余篇SCI,达博士毕业要求后,他获顶级名校全奖直博offer
- C/C++ OpenCV方框滤波
- 赞!卷积神经网络中十大拍案叫绝的操作
- Windows:将cmd命令行添加到右键中方法
- XP硬盘分区软件测试面试,怎样让WinXP系统自检并修复硬盘?
- Delphi XE2 - 点点滴滴设置
- HBase 的(伪)分布式安装
- 计算机网络运输层两种服务,计算机网络体系结构及协议之运输层
- layim在线客服 架构实现
- 木瓜移动再求上市:毛利率走低、盈利能力弱,沈思“迷恋”相亲
- 【C++】模板类的友元函数
- ROS学习记录(二)玩转海龟——海龟保姆级教程
热门文章
- android 华为pad 自动对焦,有料更有“生产力”,华为平板M6体验,补足安卓生态短板...
- list在python里是什么意思_python中的list是什么意思
- 通过Maven仓库安装Spire.PDF for Java
- minecraft刷怪笼java_Minecraft Java版 21w03a 发布
- 车机安卓4.4刷机包_一加5/5T机型获得氢OS公测版内测-基于安卓10底层新版刷机包...
- 第八章、路由观念与路由器设定
- pandas 中diff 一阶差分的理解
- 视频教程-79节PS入门基础视频教程-Photoshop
- Qt实现哈夫曼编码解压缩软件详解
- Javaweb课后习题