likes
comments
collection
share

Android Gradle编译介绍

作者站长头像
站长
· 阅读数 31

本文主要在于介绍安卓Gradle的编译过程,希望大家看完之后可以对安卓的基本编译过程有所了解,同时在拿到一个新的安卓项目之后,能够快速理解项目架构以及上手项目。

一、Gradle 构建流程

官方文档:Build Lifecycle

Gadle构建大致分三步,初始化->配置->执行

Android Gradle编译介绍

概念

在构建流程之前,先介绍Gradle相关的知识

Gradle Build Tool is a fast, dependable, and adaptable open-source build automation tool with an elegant and extensible declarative build language.

  1. Gradle采用Groovy或者Kotlin DSL编写脚本

  2. Project的默认构建脚本为build.gradle,新建子项目时自动添加

  3. Gradle 提供了Plugin机制、Task机制,Gradle执行是按照生成的Task去执行的

  4. Gradle编译过程是个有向无环图任务(DAG)

  5. Gradle 是跑在 JVM 上面的,xxx.gradle 最后编译出来的就是 class 字节码。所以Gradle脚本中每一个闭包都可以简单的理解为是一个个的方法。

    1.   比如 settings.gradle中的include其实也是一个方法
    2. Android Gradle编译介绍
    3. Android Gradle编译介绍Android Gradle编译介绍

初始化(Initialization)

初始化阶段,分两步,一步是init Script,Gradle首先读取解析gradle.properties,配置全局参数。第二步,会去找读取解析settings.gradle,生成Settings对象,并生成相应子项目的Project对象。

配置(Configuration)

配置阶段则是根据settings.gradle中的子项目找到相应的build.gradle去编译并执行同时构造Task任务,并根据Task的依赖关系,生成一个基于Task的有向无环图TaskExecutionGraph。子项目的build.gradle查找是广度优先查找且随机。

执行(Executation)

Gradle执行过程就是执行Task的过程,通过读取配置阶段生成有向无环图TaskExecutionGraph,按顺序依次执行各个Task。

Gradle Hook

Gradle Hook是针对构建生命周期的每个阶段都有回调钩子函数,使得开发者可以自定义功能。

做个测试,settings.gradle内容如下

rootProject.name = "AndroidDemo"
include ':app'
include ':library'
include ':plugin'
rootProject.buildFileName = "build_test.gradle" //指定编译的gradle脚本

gradle.settingsEvaluated {
println "gradle settingsEvaluated settings.gradle 解析完成之后被调用"
}
gradle.projectsLoaded {
println "gradle projectsLoaded 参与构建的 Project 对象创建完毕之后被调用"
}
gradle.beforeProject {
println "gradle beforeProject 每个Project执行build.gradle配置代码之前被调用"
}
gradle.projectsEvaluated {
println "gradle projectsEvaluated 所有Project都执行完对应的build.gradle配置代码,准备生成对应的Task依赖图"
}
gradle.taskGraph.whenReady {
println "gradle taskGraph whenReady Task 依赖关系已经建立完毕"
}

gradle.taskGraph.beforeTask {
println "gradle taskGraph beforeTask 每一个 Task 执行之前被调用"
}
gradle.taskGraph.afterTask {
println "gradle taskGraph afterTask 每一个 Task 执行之后被调用"
}
gradle.buildFinished {
println "gradle buildFinished 构建结束之后被调用"
}

gradle.addProjectEvaluationListener(
        new ProjectEvaluationListener() {
            //执行各个project的beforeEvaluate:在配置阶段完成
            @Override
            void beforeEvaluate(Project project) {
                println "$ { project.name } beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行"
            }

            //执行各个project的afterEvaluate:在配置阶段完成
            @Override
            void afterEvaluate(Project project, ProjectState projectState) {
                println "$ { project.name } afterEvaluate在配置阶段结束之后,执行阶段之前被调用" }
})

然后在根项目的build_test.gradle中引入build_plugin.gradle

...
//引入gradle
apply from: './build_plugin.gradle'

build_plugin.gradle内容如下:

// 项目内直接写一个task
task("my_test") {
doLast {
System.out.println("============== I'm a test task!!! ==============")
    }
// beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行
    beforeEvaluate {
System.out.println("============== I'm a test task!!! beforeEvaluate ==============")
    }

afterEvaluate {
System.out.println("============== I'm a test task!!! afterEvaluate ==============")
    }
}

// 项目内直接写一个Plugin
class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
doLast {
println 'Hello from the build.gradle GreetingPlugin'
            }
}
project.task('world') {
doLast {
println 'Hello World from the build.gradle GreetingPlugin'
            }
dependsOn('my_test')
            dependsOn('hello')
        }
}
}

//使用plugin
apply plugin:GreetingPlugin

执行./gradlew :projects,打印如下:

gradle settingsEvaluated settings.gradle 解析完成之后被调用
gradle projectsLoaded 参与构建的 Project 对象创建完毕之后被调用

> Configure project :
AndroidDemo beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行
gradle beforeProject 每个Project执行build.gradle配置代码之前被调用
AndroidDemo afterEvaluate在配置阶段结束之后,执行阶段之前被调用
============== I'm a test task!!! afterEvaluate ==============

> Configure project :app
app beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行
gradle beforeProject 每个Project执行build.gradle配置代码之前被调用
NDK is missing a "platforms" directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory.  It is currently set to /Users/bytedance/Library/Android/sdk/ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.

app afterEvaluate在配置阶段结束之后,执行阶段之前被调用

> Configure project :library
library beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行
gradle beforeProject 每个Project执行build.gradle配置代码之前被调用
NDK is missing a "platforms" directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory.  It is currently set to /Users/bytedance/Library/Android/sdk/ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.

library afterEvaluate在配置阶段结束之后,执行阶段之前被调用

> Configure project :plugin
plugin beforeEvaluate在解析build.gradle文件前执行,写在build.gradle中时并不会执行
gradle beforeProject 每个Project执行build.gradle配置代码之前被调用
plugin afterEvaluate在配置阶段结束之后,执行阶段之前被调用
gradle projectsEvaluated 所有Project都执行完对应的build.gradle配置代码,准备生成对应的Task依赖图
gradle taskGraph whenReady Task 依赖关系已经建立完毕

> Task :projects
gradle taskGraph beforeTask 每一个 Task 执行之前被调用

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'AndroidDemo'
+--- Project ':app'
+--- Project ':library'
--- Project ':plugin'

To see a list of the tasks of a project, run gradlew <project-path>:tasks
For example, try running gradlew :app:tasks
gradle taskGraph afterTask 每一个 Task 执行之后被调用
gradle buildFinished 构建结束之后被调用

二、Gradle Plugin 插件编写

AGP,Android Gradle Plugin,就是安卓提供的执行安卓操作的Gradle 插件。

我们自己也可以开发一个Gradle 插件进行功能开发,感兴趣可以看一看 Developing Custom Gradle Plugins,这里只是简单介绍两种方式。

这里提供下Demo github地址:github.com/JuanJuanQin…

Gradle Plugin开发其实就是Java开发,因为最终Gradle也是跑在JVM上的。

Gradle脚本中实现

这里我也写了比较简单的例子,插件就是Task的合集,当我实现了一个Task或者plugin,我就可以在相应的项目下找到对应的Task任务。

Android Gradle编译介绍

Plugin工程

如果是一个plugin工程,build.gradle就需要apply plugin:'java-gradle-plugin',同时也可以直接使用gradle命令行工具。

Android Gradle编译介绍

之后就可以像开发Java一样进行开发就好。

Android Gradle编译介绍

三、Android 工程目录结构

Android Studio工程编译是基于Gradle的自动化编译。

比如我新建了一个简单的演示Demo,里面包括了app、library、plugin三块内容。

其目录结构如下:

.
├── app
│   ├── build.gradle # 子项目的构建脚本
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── build.gradle # 根项目的构建脚本
├── build_test.gradle # 根项目的构建脚本(测试使用) 
├── gradle
│   └── wrapper
├── gradle.properties #Gradle脚本的全局配置
├── gradlew
├── gradlew.bat
├── library
│   ├── build.gradle # 子项目的构建脚本
│   ├── consumer-rules.pro
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── local.properties
├── plugin
│   ├── build.gradle # 子项目的构建脚本
│   ├── libs
│   └── src
├── local.properties #本地配置
└── settings.gradle # 工程配置脚本

一般我们遇到的Android 工程都是multi-project即多模块项目,每项目模块有着自己的构建脚本build.gradle,而根项目会多了一个settings.gradle,并且根项目的build.gradle定义的规则是适用于工程中所有模块。

settings.gradle

工程配置文件,当项目是多模块架构时,一般用于指明项目名称、参与构建的模块、模块的构建脚本等等。

rootProject.name = "AndroidDemo" //指定根目录名称
include ':app'                   // 指定参与构建的模块
include ':library'
include ':plugin'
rootProject.buildFileName="build_test.gralde"// 指定根项目的构建脚本

我们可以通过 ./gradlew :projects来打印参与构建的项目。

Android Gradle编译介绍

RootProject的build.gradle

下面是根工程的build.gradle配置

buildscript { // 这里是gradle脚本自身所需依赖
repositories { //gradle 引用的库的位置,按顺序找
mavenLocal()//本地库
        maven {
          url 'xxx' //自定义的maven地址
        }
        mavenCentral()//Maven
        google()//Android Studio3.0后新增,可引用google上的开源项目
        jcenter()//类似于github的代码托管仓库,配置可引用 jcenter上的开源项目
    }
    //gradle脚本自身依赖
dependencies {
classpath("com.android.tools.build:gradle:3.4.3")// AGP插件,android gradle plugin
        classpath("com.guardsquare:proguard-gradle:7.2.1")// 代码混淆插件
    }
}

// 对工程内所有项目生效
allprojects {
    // 各项目模块的依赖来源
repositories {
        maven {
          url 'xxx' //自定义的maven地址
        }
mavenLocal()
        mavenCentral()
        google()
        jcenter()
    }
}
//根项目的依赖
dependecies {

}

buildscript 内容是针对gradle脚本自身的依赖,而非工程内容依赖。

在依赖引入中,buildscript用的classpath所定义的是gradle脚本依赖项的路径。通过将依赖项添加到classpath中,Gradle脚本编写时使用calsspath里提供的类,从而提供特定的功能。

SubProject的build.gradle

下面是子模块的build.gradle配置

// 使用的plugin,此处相当于 apply plugin: 'com.android.application'
plugins {
id 'com.android.application'//产物生成  应用apk
    //id 'com.android.library' 产物生成     库aar
    //id 'java-gradle-plugin'  产物生成     Gradle plugin
}

// android extension扩展项,非方法
android {
compileSdkVersion 33    // 编译时的SDK版本

    defaultConfig {
applicationId "com.bytedance.android.demo"// 项目包名
        minSdkVersion 19        // 支持的最低SDK版本
        targetSdkVersion 33    // 目标SDK版本
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    // 编译产物
buildTypes {
release {
            // 是否对代码进行混淆
minifyEnabled false
            // 混淆文件位置
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
}
    // 编译指定为 java8,这样才可以用 java8 中的 lamda 表达式
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
 }
}
// 项目依赖
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
} 

plugin内容一般根据子项目作用来决定。

compileSdkVersion是Gradle编译时的SDK版本,不决定运行时行为,在编译过程中会告警/报错API使用问题。

targetSdkVersion是目标SDK版本,为的是兼容旧SDK版本提供的API,手机系统升级后,Android SDK提供新特性的同时会保留老特性,而新老特性选择会根据targetSdkVersion

dependencies是子项目工程依赖的库,这里主要有以下几种依赖方式:

implementation:编译且运行时依赖,依赖库不对外暴露

api:编译且运行时依赖,依赖库对外暴露

compileOnly:仅编译时依赖,运行时取决于宿主是否依赖

runtimeOnly:仅运行时依赖,编译时不依赖

apiimplementation两种依赖方式,导致编译、运行时库的依赖引用有所不同。

子项目A分别通过四种方式依赖引入的库X,项目B依赖子项目A,项目B对库X的编译/运行时不同表现

子项目A通过api方式依赖引入的库X,项目B编译时可以直接访问库X,运行时引入库X

子项目A通过implementation方式依赖引入库X,项目B编译时无法访问库X,运行时引入库X

gradle-wrapper

gradle目录下主要是跟gradle-wrapper相关内容,主要用于在项目中自动管理 Gradle 的安装和版本。

├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties

grade-wrapper.properties用来放置各种Gradle 相关的配置,比如下载地址,下载存放位置

#GRADLE_USER_HOME 默认放置位置。 /Users/{your_name}/.gradle
distributionBase=GRADLE_USER_HOME 
#下载位置下的相对路径
distributionPath=wrapper/dists
#下载地址、版本
distributionUrl=https://services.gradle.org/distributions/gradle-6.1.1-bin.zip
#解压位置
zipStoreBase=GRADLE_USER_HOME
#解压位置下相对路径
zipStorePath=wrapper/dists

gradle.properties

gradle.properties位于根目录下,是 Gradle 的一个配置文件,用于存储项目的全局属性和设置。

gradle.properties文件中,我们可以定义各种属性,例如:

  • 配置 Gradle 的行为,例如设置日志级别或禁用某些警告。
  • 定义项目特定的常量,例如版本号、API 密钥等。
  • 配置依赖管理相关的设置,例如 Maven 仓库地址。
  • 设置环境变量,例如开发、测试或生产环境的不同配置。

这些属性可以在 Gradle 的构建脚本中通过${}语法进行引用,比如:

Android Gradle编译介绍 Android Gradle编译介绍

local.properties

这个配置是Android Studio生成项目生成的,主要用来放置一些本地变量,如sdk、ndk的目录。

四、Android Gradle编译执行

配置变体

Configure Build Variants

变体配置可以使得我们的应用打出不同版本,不同需求的包,比如可以通过变体(Build Variants)来实现国内海外版本的不同。

Android Gradle编译介绍

打包流程

根据module的plugin配置,最终打出来的产物也各不相同,我们主要以AppPlugin的打包作为主。

AppPlugin: apk

DynamicFeaturePlugin: aab

LibraryPlugin: aar

Android Gradle编译介绍

执行./graldew :app:assRelease -s就是执行打包的过程

其执行的Task:

checkReleaseClasspath  检查compile和runtime classpath版本是否一致
preBuild   空task,锚点
extractProguardFiles  把默认proguard文件放到build目录
preReleaseBuild   空task,锚点
compileReleaseAidl   处理aidl
compileReleaseRenderscript   处理renderscript
checkReleaseManifest   检查manifest文件是否存在
generateReleaseBuildConfig   生成BuildConfig
prepareLintJar   拷贝lint.jar到指定位置
mainApkListPersistenceRelease 生成apk基本信息到apk-list.json文件里
generateReleaseResValues   生成values.xml里的res
generateReleaseResources   空task,锚点用
mergeReleaseResources  合并所有的android资源文件,并生成flat文件
createReleaseCompatibleScreenManifests 生成manifest里的compatible-screens配置
processReleaseManifest  合并manifest文件
splitsDiscoveryTaskRelease  生成split-list.json,分apk
processReleaseResources  处理android resources
generateReleaseSources   空task,锚点
javaPreCompileRelease  编译前处理,比如生成annotationprocessors.json
compileReleaseJavaWithJavac  编译java文件
compileReleaseNdk   ndk编译c++源文件生成so
compileReleaseSources   空task,锚点用
mergeReleaseShaders   合并shader文件
compileReleaseShaders  编译shader文件
generateReleaseAssets  空task,锚点
mergeReleaseAssets  合并assets文件
checkReleaseLibraries  multi-apk检查
processReleaseJavaRes  处理java resources
transformResourcesWithMergeJavaResForRelease    合并java res
transformClassesAndResourcesWithProguardForRelease  执行proguard
transformClassesWithDexBuilderForRelease  class转换成dex
transformClassesWithMultidexlistForRelease  生成main dex list
transformDexArchiveWithDexMergerForRelease 合并dex
transformClassesAndDexWithShrinkResForRelease  删除无用资源
mergeReleaseJniLibFolders   合并jnilib文件
transformNativeLibsWithMergeJniLibsForRelease  合并jnilibs
transformNativeLibsWithStripDebugSymbolForRelease  删除debug符号
packageRelease  打包
assembleRelease 空task,锚点

一般到了transform的task执行,我们都可以默认打包成功!因为transform阶段主要就是class文件转换dex文件过程。

Android Gradle编译介绍

工程的资源文件(res文件夹下的文件),通过AAPT打包成R.java类(资源索引表),以及.arsc资源文件

如果有aidl,通过aidl工具,打包成java接口类

R.java和aidl.java通过java编译成想要的.class文件

源码class文件和第三方jar或者library通过dx工具打包成dex文件。dx工具的主要作用是将java字节码转换成Dalvik字节码,在此过程中会压缩常量池,消除一些冗余信息等

apkbuilder工具会将所有没有编译的资源,.arsc资源,.dex文件打包到一个完成apk文件中中

签名,5中完成apk通过配置的签名文件(debug和release都有),jarsigner工具会对齐签名。得到一个签名后的apk,signed.apk

zipAlign工具对6中的signed.apk进行对齐处理,所谓对齐,主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用主要是为了减少运行时内存的使用。

Copy From Android AAPT详解

Transform

A Transform that processes intermediary build artifacts.

For each added transform, a new task is created. The action of adding a transform takes care of handling dependencies between the tasks. This is done based on what the transform processes. The output of the transform becomes consumable by other transforms and these tasks get automatically linked together.

The Transform indicates what it applies to (content, scope) and what it generates (content).

在上图打包过程中,Android允许开发者自写Gradle Plugin插件来干预.class 文件到.dex文件的转换,开发者进一步可以使用ASM 等字节码编辑工具进行修改,插入逻辑,比如方法替换等,这也是热修常常采用的方式之一。

Transform 是依靠 Task 执行的,在配置阶段,Gradle 会为每个加入的Transform 创建对应的Task,添加Transform的操作负责处理任务之间的依赖关系。并且Transform是一个流式过程,前一个Transform Task的输出将作为下一个Transform Task的输入。

 class MyTransform extends Transform {
    @Override
    public String getName() {
        return "MyTransform";
    }

    //处理的数据类型
    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }
    //作用域
    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    //是否开启增量编译  
    @Override
    public boolean isIncremental() {
        return false;
    }

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        System.out.println("============== MyTransform Test ==============");
    }
}

Transform实现以后,需要自己添加到使用到的模块对应Plugin的Extension

 class GreetingPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register a transform to AppExtension
        AppExtension appExtension = project.getExtensions().findByType(AppExtension.class);
        appExtension.registerTransform(new MyTransform());

        // Register a task
        project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println("Hello from plugin 'com.test.greeting.greeting'"));
        });
        project.getTasks().register("greeting2", task -> {
            task.doLast(s -> System.out.println("Hello2 from plugin 'com.test.greeting.greeting'"));
        });
    }
}

在app下,执行assRelease后,我们看到我们的Transform已经被添加进来了。

Android Gradle编译介绍

参考:

Gradle | Extension扩展详解_gradle appextension-CSDN博客

深入理解 Gradle 框架之一:Plugin、Extension、buildSrc | Henley

Android AAPT详解

【Android Gradle 插件】Gradle 自定义 Plugin 插件 ③ ( 自定义插件作用 | Android Gradle 插件的扩展 | 自定义 Extension 扩展 )

转载自:https://juejin.cn/post/7382891974942490650
评论
请登录