Android 构建流程

安卓工程依赖 Gradle 工具来完成构建,Gradle 是以 Groovy 语言为基础,面向 Java 应用为主,基于 DSL(领域特定语言)语法的自动化构建工具

Gradle 依赖冲突

https://cloud.tencent.com/developer/article/1742859

1.统一版本管理

当一个 Project 下有多个 module 或 library 时很适合使用统一版本管理方式。在 Project 目录下创建 config.gradle 文件,将需要统一管理的加在里面

// config.gradle
ext {
 //Dependencies
 supportLibraryVersion = '25.3.1'
 okHttpVersion = '3.8.0'
 domainTestDependencies = [
   appcompatv7: "com.android.support:appcompat-v7:${supportLibraryVersion}",
   okHttp  : "com.squareup.okhttp3:okhttp:${okHttpVersion}"
 ]
}

apply from: "config.gradle"
implementation rootProject.ext.dependencies["appcompatv7"]
implementation rootProject.ext.dependencies["okHttp"]

2.去除冲突依赖

 implementation ('com.carlos.test:Test:1.0.0') {
  exclude group: "io.reactivex.rxjava2",module: "rxjava"
  // exclude group: "io.reactivex.rxjava2:rxjava:2.1.11"
 }
 implementation 'io.reactivex.rxjava2:rxjava:2.1.13'

 packagingOptions {
      pickFirst 'lib/arm64-v8a/libgnustl_shared.so'
      pickFirst 'lib/armeabi-v7a/libgnustl_shared.so'
 }

3.强制使用某版本依赖

我们也可以直接在策略里面强制使用某版本依赖

configurations.all {
 resolutionStrategy {
  force 'io.reactivex.rxjava2:rxjava:2.1.13'
 }
}

4.依赖适配接口

适用于公司二方库频繁变动的场景(RTC)

5.代码适配

如果依赖的大版本不同,则只能适配代码

例: AA.BB.CC-SNAPSHOT

  1. AA表示大版本号,不与之前的版本号兼容
  2. BB表示中版本号,一般代表功能增加
  3. CC表示小版本号,一般代表修复bug
  4. 版本状态的可选值有:SNAPSHOT、RELEASE

Gradle 构建流程

  • 初始化阶段:加载 Settings 脚本,创建 Project 对象,建立 Projects 层级结构
  • 配置阶段:根据 build.gradle 脚本配置各个 Project,并生成 TaskGraph,配置结束后调用 Project 的 afterEvaluate 方法
  • 执行阶段:根据 Gradle 命令执行具体的 Task

在 Gradle 配置阶段,我们可以通过 apply 方法执行插件逻辑,添加 Task 任务(默认继承Default Task),通过 ext 关键字或 Project.property(String) 来定义 Property

Gradle 依赖

  • 依赖类型:Maven 库、本地 Project、Files
  • 依赖方式:implementation、api、compileOnly、runtimeOnly
  • 排除依赖:通过 exclude 的方式来排除指定的引用
dependencies {
    implementation('some-library') {
        exclude group: 'com.example.imgtools', module: 'native'
    }
}

自定义Task

继承 DefaultTask,执行函数需要通过 TaskAction 标注。在实际应用中,我们都是在 doLast 中执行逻辑

Gradle创建Task的几种方式:通过 Project 的 task 方法、通过 TaskContainer 的 create 方法

project.afterEvaluate {
    HashMap<String, String> args = new HashMap<>()
    args.put(Task.TASK_GROUP, "MyGroup")
    project.task(args, MY_TASK_NAME).doLast {
        // doSomething
    }
}

自定义 Plugin

  • 添加 localGroovy 和 gradleApi 依赖
  • apply groovy 插件
  • 在 src/main/groovy 中创建插件入口类并实现 Plugin<Project> 接口
  • 在 src/main/resources/META-INF/gradle-plugins/ 目录下创建 xxx.properties 文件来指定插件入口类
  • apply maven 插件,用来发布插件
// build.gradle

apply plugin: 'groovy'
apply plugin: 'maven'

dependencies {
    implementation gradleApi()
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:3.4.1'
    implementation 'com.google.code.gson:gson:2.8.0'
}

// gradle.properties

# 组件组名
ARTIFACT_GROUP=xxx
# 组件名
ARTIFACT_NAME=xxx
# maven 仓库正式版地址
REPOSITORY=https://maven.byted.org/repository/xxx_android/
# maven 仓库快照版地址
REPOSITORY_SNAPSHOT=https://maven.byted.org/repository/xxx_android_snapshots/
#版本号
ARTIFACT_VERSION=8.8.8

Android 构建变体

构建变体(BuildVariant)通过 ProductFlavor 和 BuildType 组成

productFlavors {

    branchOne {
        applicationId "com.example.branchOne"
        buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com"
    }

    branchTwo {
        applicationId "com.example.branchTwo"
        buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org"
    }
}

// 我们可以在代码中使用 BuildConfig,上面的的例子就是BuildConfig.CONFIG_ENDPOINT

我们可以单独为某一个 Variants 添加依赖,只需要在依赖方式前加上对应的 Variant 名字前缀即可

dependencies {

    implementation 'com.android.support:support-v4:22.2.0'
    branchOneimplementation 'com.android.support:appcompat-v7:22.2.0'
}

和 productFlavors 类似,还有 buildTypes(debug 和 release 是默认的),它们都也可以为你的应用程序生成 Variant,合并一起形成 BuildVariant

BuildType 和 ProductFlavor 的区别

  • ProductFlavor 可以改变应用的代码,通过 ProductFlavor 我们可以改变 ApplicationId,增加 BuildConfig 信息
  • BuildType 不会改变程序的代码,通过 BuildType 可以配置混淆规则、签名规则、压缩规则等

变体冲突

  • 对于应用中存在,库中不存在的风味类型和构建类型,使用matchingFallbacks来指定替代的类型(顺序排列)
  • 对于应用中没有,库中存在的风味维度或构建维度,使用missingDimensionStrategy来指定默认的类型(顺序排列)

Android 构建流程

  1. 编译器将源代码转换成 DEX 文件,并将其他所有内容转换成编译后的资源
  2. 打包器将 DEX 文件和编译后的资源组合成 APK 或 AAB
  3. 打包器使用调试或发布密钥库为 APK 或 AAB 签名
  4. 在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,以减少其在设备上运行时所占用的内存

编译速度优化

避免编译和打包不测试的资源(例如,其他语言本地化和屏幕密度资源)。为此,您可以仅为“dev”变种的版本指定一个语言资源和屏幕密度

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resConfigs("en", "xxhdpi")
        }
        ...
    }
}

在 build.gradle 文件中声明依赖项时,您应当避免在结尾处使用带加号的版本号,例如 'com.android.tools.build:gradle:2.+'。使用动态版本号可能会导致意外的版本更新和难以解析版本差异,并会因 Gradle 检查有无更新而减慢构建速度。您应该使用静态/硬编码版本号

Android Gradle 插件 3.3.0 及更高版本改进了对增量注解处理的支持。因此,如需提高增量构建速度,您应更新 Android Gradle 插件并尽可能仅使用增量注解处理器

详细构建流程

  • 首先 AAPT 工具会编译资源文件并生成对应资源 ID 的 R 文件和 ARSC 资源文件
  • AIDL 工具将其中的 AIDL 接口转化成 Java 文件
  • Java Compiler 将 Java文件编译成 class 文件
  • 执行Transform Task
  • 通过 dx 工具将 class 文件转化为 dex 文件
  • 通过签名工具对其进行签名并生成 Apk 文件
  • 通过 zipalign 进行优化,提升运行速度

ZipAlign 会将 Apk 文件中未压缩的数据在 4 个字节边界上对齐,提高资源查找速度,但同时也会增加包大小

annotationProcessor & kapt

annotationProcessor 是 Annotation Processing Tool 工具中的一种,随着Android Gradle Plugin 2.2 版本的发布,Android Gradle 插件提供了名为 annotationProcessor 的功能来完全代替 android-apt,不需要引入,可以直接在 build.gradle 文件中使用

kapt 也是 APT 工具的一种,使用 Kotlin 开发 Android 应用时,要在预编译阶段处理注解必须使用 kotlin-kapt 而不能使用 annotationProcessor,kotlin-kapt 不是 Android Gradle 内置插件需要额外引入

apply plugin: 'kotlin-kapt'
...
kapt ‘com.google.dagger:dagger-android-processor:2.25.4’

Transform

Transform 是 Android 官方提供给开发者在项目构建阶段(class -> dex转换期间)用来修改 class文件的一套标准API。目前比较经典的应用是字节码插桩、代码注入等

每个 Transform 其实都是一个 gradle task,Android 编译器中的 TaskManager 将每个 Transform 串连起来,第一个 Transform 接收来自 javac 编译的结果,以及已经拉取到在本地的第三方依赖(jar、aar),还有 resource 资源,注意,这里的 resource 并非 android 项目中的 res 资源,而是 asset 目录下的资源

Transform 基础概念

  • TransformInput
  • TransformOutputProvider

TransformInput 是指输入文件的一个抽象,包括:

  • DirectoryInput 指以源码的方式参与项目编译的所有目录结构及其目录下的源码文件
  • JarInput 指以 jar/aar 包方式参与项目编译的所有本地 jar/aar 包和远程 jar/aar 包

TransformOutputProvider 之 Transform 的输出,通过它可以获取到输出路径等信息

class CustomTransform extends Transform {

    final String NAME = "CustomTransform"

    @Override
    String getName() {
        return NAME
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) {
        super.transform(transformInvocation)

        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();

        transformInvocation.inputs.each { TransformInput input ->
            input.jarInputs.each { JarInput jarInput ->
                // 处理 Jar
                processJarInput(jarInput, outputProvider)
            }

            input.directoryInputs.each { DirectoryInput directoryInput ->
                // 处理源码文件
                processDirectoryInputs(directoryInput, outputProvider)
            }
        }
    }

    void processJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
        File dest = outputProvider.getContentLocation(
                jarInput.getFile().getAbsolutePath(),
                jarInput.getContentTypes(),
                jarInput.getScopes(),
                Format.JAR)
                
        // to do some transform
        
        // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了        
        FileUtils.copyFiley(jarInput.getFile(), dest)
    }

    void processDirectoryInputs(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
        File dest = outputProvider.getContentLocation(directoryInput.getName(),
                directoryInput.getContentTypes(), directoryInput.getScopes(),
                Format.DIRECTORY)
        // 建立文件夹        
        FileUtils.forceMkdir(dest)
        
        // to do some transform
        
        // 将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了        
        FileUtils.copyDirectory(directoryInput.getFile(), dest)
    }
}

合并清单

https://developer.android.com/studio/build/manifest-merge?hl=zh-cn

Annotation Processor & Transform 如何选择

类新增场景还是类增强场景?

  • 类新增且模块间存在严格的依赖关系则考虑APT,方便编译时语法检测以及在build过程中生成对应的辅助类
  • 类增强应该使用Transform来修改Class字节码,防止污染源文件
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值