Gradle编译打包Android apk详细介绍
Gradle编译打包Androidapk详细介绍
理解Gradle构建过程,解读AndroidGradle插件的配置
阅读本文一定是要使用过Gradle生成apk,文中不会讲如何安装运行Gradle,如有需要可先看文末的参考文章。
APK包是一个ZIP压缩包,从Java源代码、资源文件到生成这个APK,经过了编译打包一系列特定的过程,SDK文档(/docs/tools/building/index.html)中找到。而这一系列特定的过程,重复繁琐,构建工具(buildtool)就是来流程化这些过程,解放你的双手。Ant作为apk早期的构建工具,构建过程显得很直观,像配置;Gradle可以方便地配置,但更像脚本,可以编程。
理解Gradle构建
1.简单理解构建工具
从一个程序员的角度,你该如何编写代码来自动化你的apk生成过程呢?首先得知道你需要的SDK、NDK在什么位置,Android工程有几个库工程,它们的Java源代码、资源文件分别有哪些?命令行的输入参数肯定无法满足需求,那自然而然想到配置文件。因此你的自动化工具就是解析这些配置文件,按照生成apk文件要求执行的程序。Gradle就是这样的工具程序,配置文件就是你常见的settings.gradle,build.gradle,不过他还提供了更多的功能,如依赖管理,流程控制,还有插件机制来定制你的生成过程。
Gradle的编程语言是Groovy,其需要的配置文件支持Groovy。Groovy语言像Java一样是基于JVM的,而且能够很好的支持Java,因此可以用Java代码编写扩展插件,像普通编程一样来写配置文件,而不用像Ant一样用xml来编写配置逻辑。
2.Groovy
Groovy的语法,把自己的代码缩略的看上去像脚本,本人也只是看了一点点文档,列出我们常用的介绍一下:
首先Groovy是面向对象的动态语言
(1)语句的末尾可省略分号
(2)变量定义可以用def,也可以直接使用
(3)函数定义可以用def,也可以不用(有返回值声明也可)
函数可省略参数类型
函数调用可省略括号
来看个例子:
println'Hello' intpower(intn){2**n} println"2^6==${power(6)}"
第一行输出字符串Hello,第二行定义一个函数,第三行输出函数调用值,双引号中间的${}可被解析成表达式运行。
(4)List和Map类型
List实现就是Java的java.util.ArrayList,变量由[]包围,用逗号分隔,比如
defheterogeneous=[1,"a",true]//其元素可以是任何对象
Map的实现是java.util.LinkedHashMap,也是由[]包围,用逗号分隔,其中的键值对是key:value形式,如:
defcolors=[red:'#FF0000',green:'#00FF00',blue:'#0000FF'] assertcolors['red']=='#FF0000'//取值可以[key]或者.key的形式 assertcolors.green=='#00FF00'
(5)闭包(Closures)
闭包我的理解类似C里的函数指针,或者说函数对象,可以像函数一样调用的对象
{[closureParameters->]statements}
例子
deftestClosure={intarg1,Stringarg2->//def可省略,参数可省略,默认有it,当只有->表示没有参数 println"arg2:${arg2}"//执行的代码,返回值是最后一句,也可以用return } 调用: testClosure(1,'2') testClosure.call(1,'2')
3.Gradle
接着说自动化工具,编程语言有了,那实现一系列特定过程生成apk,就看如何实现了。Gradle里有Project,表示一个待编译打包处理的工程,可以生成apk,可以生成Jar,Project中可以包含多个Project;每个Project由很多的Task构成,可以理解为不同的过程;每个Task里又是有不同的Action,和一系列要执行的操作(或者说你写的要执行的语句)。Project中的Task的执行顺序,则是由其dependsOn来控制的。
Gradle执行时是以task为单位执行,在命令行中以gradletask来执行,这一过程的生命周期分为三个阶段(官方使用手册中也有详细介绍TheBuildLifecycle):
(1)初始化阶段
判断包含哪些工程,创建对应的Project实例,可以看到最新执行的是在settings.gradle中的语句
(2)配置阶段
创建不同的Task(Task可以动态生成),并根据Task之间的dependsOn,确定Task的执行顺序,或者禁用某些Task。此阶段完成后,Task之间的依赖关系也就确定下来。
(3)执行阶段
在配置完成后,按照依赖关系,按顺序执行。
需要注意的是通常在Task中的语句,都是在配置阶段执行的,而doFirst,doLast这类Action是在执行阶段中的。因此会出现你的依赖关系中没有的Task中语句也被执行了的问题
如下例子,打印出的task内容是不一样的
taskprintTasksName{ tasks.all{//all是一个方法,参数可以是闭包,函数的元括号可以省略 println"showtasksinConfiguration:${it.name}"//it是闭包的默认参数 } doFirst{ tasks.all{ println"showtasksinExecution:${it.name}" } } }
4.Gradle插件和Androd插件
在提供了基本的流程控制之后,接下来是具体的要做什么,构建什么。Gradle提供了针对语言的插件如java,groovy等负责编译,集成插件如application,war等生成java可执行程序,web程序的WAR文件。Android根据APK生成的过程,编写了自己的插件,其中也使用了java插件。
(1)自定义插件的插件名称在resources/META-INF/gradle-plugins
在resources/META-INF/gradle-plugins目录下有后缀为properties文件,该文件的命名就是你在build.gradle中使用插件的名字,里面声明了该插件的实现类。在Android插件的源码中可以看到android.properties和com.android.application.properties中两个插件名称,因此在build.gradle中,应用工程使用Android插件需要applyplugin:'android'(已是deprecated)或者applyplugin:'com.android.application'
(2)Android插件中application的实现类是AppPlugin,继承自com.android.build.gradle.BasePlugin,调用apply方法,相应的configureProject(),解析local.properties,获得sdk位置,创建AndroidBuilder,应用JavaBasePlugin,而后createExtension()关联BuildType,ProductFlavor,SigningConfig,最后createTasks(),完成各个Task的创建
5.DSL(DomainSpecificLanguage)
DSL我翻译成领域专用语言,就是在这里预先规定好的规则,或者说是行话。在Gradle的DSL中一般常见的类型,一种是类型(Type)有Project、Task等,给他们定义了不同的操作和用法,一种是语句块(buildscriptblock或configurationblock)如build.gradle中常见的buildscript{},allprojects{}。Android中也定义了非常多语句块,如buildTypes{},sourceSets{}。
AndroidGradle插件配置
有了上述概念,再看android应用中的build.gradle,其实文中也只能讲一些,但是更多的可以自己查看Android插件的DSL
applyplugin:'com.android.application'//使用Android插件,非库工程,生成的是apk dependencies{ compile'com.android.support:multidex:1.0.1' compilefileTree(dir:'libs',include:'*.jar') compileproject(':库工程1')//代码、资源包含在主程序apk中的 providedproject(':库工程1')//只参与编译,不输出到目标apk中 } //Android插件DSL中的AppExtension类型 android{ compileSdkVersionrootProject.ext.compileSdkVersion//多工程时配置统一的属性 buildToolsVersionrootProject.ext.buildToolsVersion //lint检查,避免lint检测到不符合条件退出编译 lintOptions{ abortOnErrorfalse } //gradle编译会默认合并库工程的manifest到主工程,如果主程序和库工程的包名不一致会有问题 enforceUniquePackageName=false //所有的productflavors继承 defaultConfig{ applicationId"cn.arainfo" minSdkVersion14 targetSdkVersion10 //multiDexEnabledtrue dexOptions{ javaMaxHeapSize"2g" jumboModetrue } } //此处由于工程是从Eclipse导入,所有路径都进行了声明 sourceSets{ main{ manifest.srcFile'AndroidManifest.xml' java.srcDirs=['src'] resources.srcDirs=['src'] aidl.srcDirs=['src'] renderscript.srcDirs=['src'] res.srcDirs=['res'] assets.srcDirs=['assets'] } debug.setRoot('build-types/debug') release.setRoot('build-types/release') } //编译类型,指定release的proguard配置文件 buildTypes{ release{ minifyEnabledtrue proguardFile'proguard.flags' } } } afterEvaluate{ println"afterEvaluatesetprojectdependsOn..." project(':主程序工程').tasks.getByName("assembleDebug").dependsOn":子程序工程:assembleDebug" if(project.hasProperty('TestRelease')){ project(':主程序工程').tasks.getByName("assembleRelease").dependsOn":子程序工程:assembleRelease" } }
说明几处
(1)如在根目录的build.gradle中声明公用属性
ext{ compileSdkVersion=21 buildToolsVersion='25.0.0' isOnWindows=Os.isFamily(Os.FAMILY_WINDOWS) }
(2)buildTypes{},productFlavors{},signingConfigs{}
上述三个语句块,类型是NamedDomainObjectContainer<T>,其中T是BuildTypeProductFlavorSigningConfig,而buildTypes{},productFlavors{}中增加新的类型,会对应有新的Task生成,规则为assemble[flavor][buildType],因此当compileSdkVersion较低,又用了MultiDex时,还想用InstantRun,就可以创建一个新的buildType,只在测试时使用
(3)afterEvaluate语句块
Android的Gradle插件版本在2.2.0时,Task的创建已经在afterEvaluate,因此,如果想继续使用tasks.getByName("assembleDebug"),必须要将自己的语句写到afterEvaluate{}语句块中
总结:
越写越心虚,零零碎碎的内容非常多,按照自己的理解贯穿下来,涵盖了部分内容,基本可以理解build.gradle。但是语句块如buildTypes{}和BuildType如何关联起来(或者说如何解析出来并创建对象)的,并没有很好的理解。
如果有时间可以好好看下《深入理解Android(一):Gradle详解》,一般的问题,比如多渠道打包productFlavors怎么配置啊,自己通过查Android的DSL就能解决。
参考文档:
1.Groovy官方文档
2.Gradle用户手册
3.Gradle的DSL
4.Android插件的DSL
5.深入理解Android(一):Gradle详解
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!