Android Gradle Plugin 源码解析之 externalNativeBuild

在Android Studio 2.2开始的Android Gradle Plugin版本中,Google集成了对cmake的完美支持,而原先的ndkBuild的方式支持也变得更加良好。这篇文章就来说说Android Gradle Plugin与交叉编译之间的一些事,即externalNativeBuild相关的task,主要是解读一下gradle构建系统相关的源码。

前言

如果你在gradle中使用过cmake,你会发现在gradle执行sync操作后,项目的module目录下就会生成一个叫.externalNativeBuild的文件夹,该文件夹用来进行C/C++代码的编译,当然,如果你用的是ndkBuild的方式,该文件夹下的文件会发生变化,文件较cmake会少很多。

cmake方式产生的文件列表如下:

而ndkBuild方式产生的文件列表如下:

他们的共同点是都有一个叫android_gradle_build.json的文件,这个文件用来被Android Gradle Plugin中的externalNativeBuild任务解析,将构建命令解析出来,然后编译C/C++代码,最后产生目标so文件。除此之外,还有x_build_command.txt和x_build_output.txt两个文件,其中x表示构建方式,使用cmake的话x就等于cmake,使用ndkBuild的话x就等于ndkBuild。x_build_command.txt文件承载着构建命令,android_gradle_build.json的生成依赖它,而x_build_output.txt文件是执行x_build_command.txt中的构建命令后控制台输出的内容。

cmake

通过查看android gradle plugin的源码,可以发现生成cmake_build_command.txt文件生成的方式其实很简单,就是一个不断拼接参数的过程,其源码在CmakeExternalNativeJsonGenerator中的getProcessBuilder,如下:

该函数的返回值为ProcessInfoBuilder,该对象专门用于携带可执行文件以及可执行文件执行时需要的参数,传递给project.exec执行。

  • 设置可执行文件为cmake,调用getCmakeExecutable方法,获取cmake可执行文件,调用setExecutable方法设置它
  • 拼接-H参数,其值为CMakeList.txt文件所在目录
  • 拼接-B参数,其值为cmake产生的中间产物,一般就是cmake_build_command.txt所在目录的父目录,cmake构建产生的中间产物全都位于此目录。
  • 拼接-G参数,其值为Android Gradle - Ninja,告诉cmake生成Android Gradle需要的项目文件,并且使用ninja构建,值得注意的是,该值在标准的cmake中是不支持的,也就是说,as使用的cmake是google修改过的,通过查看其注释 possibly remove the Android Gradle part. Depends on how upstream CMake accepts our JSON patch. 也可以看出,google可能会移除该值中Android Gradle部分,但是还是要取决于cmake如果接受google的patch。
  • 设置ANDROID_ABI参数,其值为armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64中的一个。
  • 设置ANDROID_NDK参数,其值为ndk的路径。
  • 设置CMAKE_LIBRARY_OUTPUT_DIRECTORY参数,其值为so的输出路径,一般其值为项目的build路径下的intermediates/cmake/debug/obj/$ANDROID_ABI
  • 设置CMAKE_BUILD_TYPE参数,是否是debug,其值为Debug或者Release中的一个,debug含符号信息,so很大,便于调试,Release移除了debug信息,小很多。其值来源于build.gradle中的debuggable值。
  • 设置CMAKE_MAKE_PROGRAM参数。其值为ninja路径,因为生成的是ninja构建的项目,所以需要指定其路径。该值gradle会根据sdk和ndk的路径,自动推断出。
  • 设置CMAKE_TOOLCHAIN_FILE参数,该参数是cmake用于交叉编译时设置的必要参数,主要设置一些交叉编译需要的参数,如CC,CXX,AR,AS,CFLAGS,CXXFLAGS等,可以查看NDK 交叉编译常用变量,android.toolchain.cmake文件的代码在android.toolchain.cmake,兼容android-cmake,该值gradle会根据sdk和ndk的路径,自动推断出。
  • 设置ANDROID_PLATFORM参数,一般设成和项目的最小api版本一样即可,gradle会通过它和minSdk查找出合适的值
  • 设置可选项CMAKE_C_FLAGS参数,如果不为空,则设置,其值为编译C时的一些参数
  • 设置可选项CMAKE_CXX_FLAGS参数,如果不为空,则设置,其值为编译C++时的一些参数
  • 设置可选项arguments,其值为gradle传进来的arguments参数。

更多参数说明见CMake

然后将该返回值转为字符串输出到文件中,该文件即cmake_build_command.txt

其内容大致如下:

当然我们可以直接在命令行调用之,生成cmake项目结构,如下

其对应的gradle调用代码大致如下

就是将processBuilder对象中携带的参数,调用project.exec执行即可。

executeProcess执行完之后,就会产生cmake_build_output.txt文件,该文件就是执行cmake_build_command.txt中的命令之后控制台输出的内容。大致如下:

此外,其他文件也被一并产生,如android_gradle_build.json,build.ninja,cmake_insatll.cmake,CMakeCache.txt,rules.ninja,CMakefiles文件夹等等。

而executeProcess方法,最终调用的是GradleProcessExecutor的execute方法,然后通过其内部类ExecAction的execute方法构造ExecSpec对象,调用project.exec方法执行之。其大致源码如下:

json文件生成了,之后就是生产so文件了,生成so文件由ExternalNativeBuildTask负责,其主要职责就是解析出android_gradle_build.json文件中libraries的各项中的的artifactName和buildCommand,传入对应的executeProcessBatch函数,执行buildCommand中的值,执行的方式也是通过GradleProcessExecutor的execute方法,最终产生so。

我们来看看android_gradle_build.json的大致内容:

没错解析的就是libraries下”so名字-Debug-armeabi-v7a”下的artifactName和buildCommand,我们可以试试直接将buildCommand中的命令复制到命令行执行,可以看到so就会编译产生。

除了直接复制buildCommand中的命令,也可以进入到cmake生成的文件目录,即-B指定的目录,调用ninja进行构建。如:

ninja是chromium的核心构建工具,可以参考Ninja - chromium核心构建工具学习下相关的内容。

而clean操作,则由ExternalNativeCleanTask负责,其主要职责就是解析出android_gradle_build.json中的cleanCommands命令,然后执行。

ndkBuild

和cmake类似,首先就是ndkBuild_build_command.txt的生成,其生成所需的关键参数由NdkBuildExternalNativeJsonGenerator中的getProcessBuilder函数和getBaseArgs函数负责,其代码如下:

  • 设置可执行文件为ndk-Build
  • 设置NDK_PROJECT_PATH=null
  • 设置APP_BUILD_SCRIPT参数,其值指向Android.mk文件
  • 设置NDK_APPLICATION_MK参数,其值指向Application.mk文件(如果存在的话,不存在就不会设置该参数)
  • 设置APP_ABI参数,其值为armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64中的一个。
  • 设置NDK_ALL_ABIS参数,其值等同于APP_ABI
  • 设置NDK_DEBUG参数,表示十分是debug构建,debug含符号信息,so很大,便于调试,release移除了debug信息,小很多。其值来源于build.gradle中的debuggable值。
  • 设置APP_PLATFORM参数,一般设成和项目的最小api版本一样即可,gradle会通过它和minSdk查找出合适的值
  • 设置NDK_OUT参数,其值为obj文件产生目录,一般指向项目的build路径下的intermediates/ndkBuild/$buildType/obj目录
  • 设置NDK_LIBS_OUT参数,其值为libs参数目录,用于so的存储,一般指向项目的build路径下的intermediates/ndkBuild/$buildType/lib目录
  • 设置可选项APP_CFLAGS参数,如果不为空,则设置,其值为编译C时的一些参数
  • 设置可选项APP_CPPFLAGS参数,如果不为空,则设置,其值为编译C++时的一些参数
  • 设置可选项arguments,其值为gradle传进来的arguments参数。
  • 设置APP_SHORT_COMMANDS=false
  • 设置LOCAL_SHORT_COMMANDS=false
  • 添加-B参数
  • 添加-n参数

最终生成的文件大致内容如下:

同理我们也可以直接在命令行调用他们执行

其代码调用过程同cmake,最终生成ndkBuild_build_output.txt,该文件内容就是调用ndkBuild_build_command.txt中的命令后控制台输出的内容。

而同cmake不同的是,android_gradle_build.json的文件,不再是由cmake构建系统产生,而是gradle解析ndkBuild_build_command.txt产生的。

其代码大致如下

NativeBuildConfigValue的build方法如下,总而言之就是调用各个方法,获取对应的值。

生成的json文件内容就不贴了,同cmake。

参考链接


Android Gradle Plugin 源码解析之 externalNativeBuild

发布者

默默

默默码农

发表评论

电子邮件地址不会被公开。 必填项已用*标注