前言

目前 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)

    只有动态库会被拷贝进项目工程,而静态库则是产生动态库的中间产物。在项目工程中,可以定义一个或多个 moduleAndroid.mk 文件中,或者可以使用同一份源码(.c / .cpp)在多个 module 内。

  • 构建系统会自动地为 .mk 工程处理细节问题。例如,我们不需要在 Android.mk 文件中列出源码的头文件,或者定义生成库的需要使用到的中间文件,NDK 构建工程会自动地为我们完成这些细节任务。同时新版本的 NDK toolchain / 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_VARSBUILD_SHARED_LIBRARYBUILD_STATIC_LIBRARY 这几个变量。构建系统还提供了其他种类的变量供我们在 mk 文件中使用,下面我们来一一认识了解它们吧。

NDK提供的变量

PREBUILT_SHARED_LIBRARY

该变量用来指定一个需要被依赖进工程的动态库。与 BUILD_SHARED_LIBRARYBUILD_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_PLATFORMTARGET_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.mkmy-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.mksources/foo/lib2/Android.mk 。默认地,NDK 只会遍历查找 sources/*/Android.mk 格式地目录下的 Android.mk

this-makefile

返回当前的 Makefile 文件所在文件夹的路径。

parent-makefile

返回当前 Makefile 文件位于相对文件树节点的父 Makefile 路径。

grand-parent-makefile

import-module

该函数允许我们通过 module 的名字导入另一个 module,同时自动地导入该 moduleAndroid.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

该变量声明的值会在构建 CC++ 源码文件的时候给编译器设置编译参数,是一个可选的变量。这对于指定宏定义或者编译选项是非常有帮助的。

同时官方推荐不要尝试去调整优化选项、或者调试等级在我们的 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 内敛函数在我们的 CC++ 代码中、同时允许 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 模式编译。需要注意的是,如果某个文件同时需要以 armneon 模式编译,那么 .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 将会被追加到我们 moduleLOCAL_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语法详解相关推荐

  1. Android.mk文件详解介绍

    Android.mk 编译文件是用来向 Android NDK描述你的 C,C++源代码文件的,   这篇文档描述了它的语法.在阅读下面的内容之前,假定你已经阅读了 docs/OVERVIEW.TXT ...

  2. android编译系统Android.mk使用详解

    2019独角兽企业重金招聘Python工程师标准>>> (1)Android.mk文件首先需要指定LOCAL_PATH变量,用于查找源文件.由于一般情况下 Android.mk和需要 ...

  3. Android.mk用法详解

    Android.mk是Android提供的一个makefile文件,可以将源文件分组为模块.用来引用的头文件目录.需要编译的*.c/.cpp文件.jni源文件.指定编译生成.so共享库文件或者*.a静 ...

  4. android常用头文件,Android.mk 使用详解

    和你一起终身学习,这里是程序员Android 经典好文推荐,通过阅读本文,您将收获以下知识点:一.Android.mk 简介二.Android.mk 的基本格式三.Android.mk 深入学习一四. ...

  5. Android.mk 文件语法详解

    0. Android.mk简介: Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build Syst ...

  6. Android Framework系统服务详解

    Android Framework系统服务详解 操作环境 系统:Linux (Ubuntu 12.04) 平台:高通 Android版本:5.1 PS: 符号...为省略N条代码 一.大致原理分析 A ...

  7. Android Studio 插件开发详解一:入门练手

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112003 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  8. 《Android游戏开发详解》一3.1 构造方法

    本节书摘来异步社区<Android游戏开发详解>一书中的第3章,第3.1节,作者: [美]Jonathan S. Harbour 译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区 ...

  9. 《Java和Android开发实战详解》——1.2节Java基础知识

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第1章,第1.2节Java基础知识,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

最新文章

  1. Hadoop系列之六:分布式文件系统HDFS
  2. python3.8.1安装教程-记一次Python3.8.1安装
  3. 【存储知识学习】第三章磁盘原理与技术3.2磁盘的通俗演绎和3.3磁盘相关高层技术--《大话存储》阅读笔记
  4. 智能仪表参数设定c语言,智能仪表控制系统:.doc
  5. oracle exp执行失败,Oracle EXP-EXP-00091的错误原因及处理方法
  6. hadoop自定义key,value
  7. E:Modular Stability(组合数)
  8. netbeans 定制代码_将NetBeans代码模板弯曲到我的意愿
  9. 本科生手握十余篇SCI,达博士毕业要求后,他获顶级名校全奖直博offer
  10. C/C++ OpenCV方框滤波
  11. 赞!卷积神经网络中十大拍案叫绝的操作
  12. Windows:将cmd命令行添加到右键中方法
  13. XP硬盘分区软件测试面试,怎样让WinXP系统自检并修复硬盘?
  14. Delphi XE2 - 点点滴滴设置
  15. HBase 的(伪)分布式安装
  16. 计算机网络运输层两种服务,计算机网络体系结构及协议之运输层
  17. layim在线客服 架构实现
  18. 木瓜移动再求上市:毛利率走低、盈利能力弱,沈思“迷恋”相亲
  19. 【C++】模板类的友元函数
  20. ROS学习记录(二)玩转海龟——海龟保姆级教程

热门文章

  1. android 华为pad 自动对焦,有料更有“生产力”,华为平板M6体验,补足安卓生态短板...
  2. list在python里是什么意思_python中的list是什么意思
  3. 通过Maven仓库安装Spire.PDF for Java
  4. minecraft刷怪笼java_Minecraft Java版 21w03a 发布
  5. 车机安卓4.4刷机包_一加5/5T机型获得氢OS公测版内测-基于安卓10底层新版刷机包...
  6. 第八章、路由观念与路由器设定
  7. pandas 中diff 一阶差分的理解
  8. 视频教程-79节PS入门基础视频教程-Photoshop
  9. Qt实现哈夫曼编码解压缩软件详解
  10. Javaweb课后习题