Android Gradle学习从接触到撕票【持续更新...】

本文也是我看了很多关于Gradle书籍和博客中总结出来的,还有平时自己开发的一些gradle配置使用。算是一篇总结gradle使用和科普的文章,当然如果在开发中碰到一些需要了解的gradle也会在后续对本文进行更新。

1.1 Gradle基础

       如果你想用Gradle构建你的Android项目,那么你需要创建一个构建脚本,这个脚本通常被叫作build.gradle。Gradle构建脚本的书写没有基于传统的XML文件,而是基于Groovy的领域专用语言(DSL)。

当用Android Studio创建一个新项目时,会默认生成三个Gradle文件。其中的两个文件——settings.gradle和build.gradle位于项目的根目录。另外一个build.gradle文件则在Androidapp模块内被创建。

  •  setting.gradle——settings文件在初始化阶段被执行,并且定义了哪些模块应该包含在构建内。
include ':app'
  • build.gradle(project) ——项目构建文件
buildscript {
    repositories { 
    // Gradle 4.1及更高版本包括使用google()
    //方法支持Google的Maven 仓库。你需要包含这
    //个repo来下载Android Gradle插件3.0.0或更高版本。        
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

repositories代码块将JCenter配置成一个仓库,在这种情况下,一个仓库意味着一系列的依赖包,或者说,在我们应用和依赖项目中可使用的一系列可下载的函数库。JCenter是一个很有名的Maven库。
dependencies代码块用于配置构建过程中的依赖包。这也意味着你不能将你的应用或依赖项目所需要的依赖包包含在顶层构建文件中。默认情况下,唯一被定义的依赖包是Gradle的Android插件。每个Android模块都需要有Android插件,因为该插件可使其执行Android相关的任务。
allprojects代码块可用来声明那些需要被用于所有模块的属性。你甚至可以在all-projects中创建任务,这些任务最终会被运用到所有模块。

注意:这里我们看到的是Android Gradle插件版本,而不是我们的Gradle版本,Android Gradle 其实只是 Gradle 的一个插件,是 Google 基于 Gradle 提供的插件接口所做的一些扩展。而这个插件版本也是根据gradle版本开发的,因为在我们引入项目的是时候往往总会因为插件和gradle版本不对应产生很多问题。

                           Android Gradle插件与Gradle对应图
Android Gradle学习从接触到撕票【持续更新...】_第1张图片

  • build.gradle(app)-----模块的构建文件
apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.2"
    defaultConfig {
        applicationId "practice.kagura.com.toolbarshadedemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    //recyclerview
    compile 'com.android.support:recyclerview-v7:23.2.1'
}
  • apply plugin: 'com.android.application':该插件在顶层构建文件中被配置成了依赖,这些我们在前面讨论过。谷歌的Android工具团队负责Android插件的编写和维护,并提供构建、测试和打包An-droid应用以及依赖项目的所有任务。
  • compileSdkVersion 是要用来编译应用的Android API版本。
  • buildToolsVersion 是构建工具和编译器使用的版本号。
  • defaultConfg代码块 用于配置应用的核心属性。此代码块中的属性可覆盖AndroidMani-fest.xml文件中对应的条目
  • applicationId 该属性覆盖了manifest文件中的package name
  • minSd-kVersion 被用来配置运行应用的最小API级别。
  • targetSdkVersion 用于通知系统,该应用已经在某特定Android版本通过测试,从而操作系统不必启用任何向前兼容的行为这和我们之前看到的compileSdkVersion没有任何关系
  • versionCode和versionName 在manifest文件中的作用相同,即为你的应用定义一个版本号和一个友好的版本名称
  • buildTypes代码块 可用来定义如何构建和打包不同构建类型的应用
  • dependencies代码块 是标准Gradle配置的一部分,其定义了一个应用或依赖项目的所有依赖包。默认情况下,一个新的Android应用,在libs文件夹下对所有JAR文件构成依赖。
关于依赖的几个引入配置  
新配置 弃用配置 行为
implementation compile Gradle将依赖项添加到编译类路径,并将依赖项打包到构建输出。但是,当您的模块配置implementation依赖项时,它让Gradle知道您不希望模块在编译时将依赖项泄漏给其他模块。也就是说,依赖性仅在运行时可用于其他模块。

使用这种依赖性配置,而不是 apicompile(不建议使用)可导致 显著编译时间的改进,因为它减少了构建系统需要重新编译的模块的数量。例如,如果implementation依赖项更改其API,则Gradle仅重新编译该依赖项以及直接依赖于它的模块。大多数应用和测试模块都应使用此配置。

api compile Gradle将依赖项添加到编译类路径并构建输出。当一个模块包含一个api依赖项时,它让Gradle知道该模块想要将该依赖项传递给其他模块,以便它们在运行时和编译时都可用。

此配置的行为就像compile (现在已弃用),但您应谨慎使用它,并且只能将您需要的依赖项可传递地导出到其他上游使用者。这是因为,如果api依赖项更改其外部API,Gradle将重新编译在编译时有权访问该依赖项的所有模块。因此,拥有大量api依赖项可以显着增加构建时间。除非您希望将依赖项的API公开给单独的模块,否则库模块应该使用implementation 依赖项。

compileOnly provided Gradle仅将依赖项添加到编译类路径(即,它不会添加到构建输出中)。这在您创建Android模块时非常有用,并且在编译期间需要依赖项,但在运行时将其存在是可选的。

 

如果使用此配置,则库模块必须包含运行时条件以检查依赖项是否可用,然后正常更改其行为,以便在未提供时仍可正常运行。这有助于通过不添加不重要的瞬态依赖项来减小最终APK的大小。此配置的行为就像provided (现在已弃用)。

runtimeOnly apk Gradle仅将依赖项添加到构建输出,以便在运行时使用。也就是说,它不会添加到编译类路径中。此配置的行为就像apk(现在已弃用)。
annotationProcessor compile 要添加对作为注释处理器的库的依赖关系,必须使用annotationProcessor配置将其添加到注释处理器类路径 。这是因为使用此配置可​​以通过将编译类路径与注释处理器类路径分开来提高构建性能。如果Gradle在编译类路径上找到注释处理器,它将停用 编译避免,这会对构建时间产生负面影响(Gradle 5.0及更高版本忽略编译类路径上的注释处理器)。

 

Android Gradle Plugin假定依赖是一个注释处理器,如果它的JAR文件包含以下文件:。如果插件检测到编译类路径上的注释处理器,则会产生构建错误。 
META-INF/services/javax.annotation.processing.Processor

1.1.1 依赖基础 

依赖类型 

  1. 本地模块化依赖 
    implementation project(':mylibrary')

     

  2. 本地二进制文件依赖
    implementation fileTree(dir: 'libs', include: ['*.jar'])

     

  3. 远程库依赖
    implementation 'com.example.android:app-magic:12.3'
    远程库依赖的时候,我们往往希望能引用到最新库,这时候可以使用动态化版本依赖。例如:
    dependencies {
           compile 'com.android.support:support-v4:22.2.+'
           compile 'com.android.support:appcompat-v7:22.2+'
           compile 'com.android.support:recyclerview-v7:+'
    }

    在使用动态化版本时,需要格外小心。如果你允许Gradle获取最新版本,则很可能Gradle获取的依赖版本并不稳定,它会导致构建中断。更糟糕的是,其会导致在构建服务器上和你自己的机器上运行着不同版本的依赖,从而导致应用程序的行为不一致。


 1.2 Gradle Task基础

       一个项目中有哪些任务可被使用,可以运行gradlew tasks,该命令会打印出所有可用的任务。

*在mac系统需要运行./gradlew tasks命令查看,在windows则可以直接运行gradlew task命令*

      在新建的Android项目中,其会包括Android tasks、build tasks、build setuptasks、help tasks、install tasks、verification tasks和其他tasks。

Android Gradle学习从接触到撕票【持续更新...】_第2张图片

Android插件基本任务

  • assemble:为每个构建版本创建一个APK。assemble任务默认依赖于assembleDebug和assembleRelease,如果你添加了更多的构建类型,那么就会有更多的任务。这意味着,运行assemble将会触发每一个你所拥有的构建类型,并进行一次构建操作。
  • clean:删除所有的构建内容,例如APK文件。
  • check:运行Lint检查,如果Lint发现一个问题,则可终止构建。
  • build:同时运行assemble和check。

除了扩展这些任务之外,Android插件还添加了一些新的任务。下面是一些值得注意的新任务:

  • connectedCheck:在连接设备或模拟器上运行测试。
  • deviceCheck:一个占位任务,专为其他插件在远端设备上运行测试。
  • installDebug和installRelease:在连接的设备或模拟器上安装特定版本
  • 所有的installtasks都会有相关的uninstall任务。

以上任务我们可以使用命令./gradlew  installDebug(任务名) 来运行,如果是多模块则可以使用./gradlew  :app:installDebug(任务名)来运行。(用命令可能会比在as中直接运行速度快一些)

1.2.1 自定义Task

自定义Task需要一些Groovy基础,不过熟悉Java很快就会上手,先来看如何自定义一个Task

task hello {
     println 'Hello,world!'
}

然后通过./gradlew hello 来执行task,我们就会看到gradle打印出hello world,从输出结果上看,我们会以为我们执行了这个task,但实际“Hello,world!”在执行该任务之前就已被打印出来了。为了理解发生了什么,我们需要了解Gradle构建的生产周期。在任一Gradle构建中,都有三个阶段:初始化阶段、配置阶段和执行阶段。这只是执行了配置阶段,如果我们再写一个一样的task去执行会发现两个task都被打印了出来。

Gradle生命周期

1.初始化阶段

会去读取根工程中 setting.gradle 中的 include 信息,决定有哪几个工程加入构建,创建 project 实例,比如下面有三个工程: include ':app', ':lib1', ':lib2 。

2.配置阶段

会去执行所有工程的 build.gradle 脚本,配置 project对象,一个对象由多个任务组成,
此阶段也会去创建、配置task及相关信息。

3.运行阶段

根据gradle命令传递过来的task名称,执行相关依赖任务。Task 的 Action 会在这个阶段执行。

如果你想在执行阶段给一个任务添加动作,则可以使用下面的表示法: 

task hello << {
     println 'Hello,world!'
}

可以看到task的定义只比上一个多了一个<<符合,他的意思是告知Gradle,代码在执行阶段执行,而不是在配置阶段,其实这也是doLast方法的短标记形式,与doLast相对的还有一个doFirst方法,通过这两个方法我们可以控制执行阶段代码的执行顺序。

task hello {
     println 'Configuration'
     doLast {
       println 'Goodbye'
     }
     doFirst {
       println 'Hello'
     }
}

多任务执行顺序 

如果此时有两个任务需要执行,那么如何来控制他们的执行顺序,这里也有针对task执行顺序的操作,使用mustRunAfter(),当两个任务task1和task2都需要被执行,则可以定义task2.mustRunAfter task1 这样task1就会在task2之前执行。

多任务依赖

同理如果task1需要依赖task2执行,则可以使用dependsOn(),可以使用task2.dependsOn task1,这样当我们只执行task2时他将会触发task1执行。

当然定义任务也有很多种写法,例如:只需要熟悉就好

task(hello) << {
     println 'Hello,world!'
}
task('hello') << {     
    println 'Hello,world!'
}
tasks.create(name: 'hello') << {
     println 'Hello,world!'
}

1.3 Gradle可以帮我们做什么?

1.3.1 管理签名

一个app包只有签名之后才可以发布,安装使用,而一般当我们生成release时需要使用我们生成的签名证书文件进行签名,然而我们不可能在生成release包是每次都去用签名文件进行签名,使用gradle可以自动使用我们配置好的签名信息。

最佳做法: 

  1. 在项目根目录新建keystore.properties配置文件,配置如下
    storePassword=xxxx
    keyPassword=xxxx
    keyAlias=xxxx
    storeFile=xxxx.jks
    
    以上四属性分别对应signingConfigs{}配置块。
  2. 配置signingConfigs代码块
     signingConfigs {
            release {
                keyAlias keystoreProperties['keyAlias']//签名证书密钥别名
                keyPassword keystoreProperties['keyPassword']//密钥密码
                storeFile file(keystoreProperties['storeFile'])//证书文件
                storePassword keystoreProperties['storePassword']//证书文件密码
            }
        }

    因为要获取keystore配置文件因此我们需要定义变量获取配置文件中的配置信息。

  3. // keystore.properties file, in the rootProject folder.
    def keystorePropertiesFile = rootProject.file("keystore.properties")
    
    // Initialize a new Properties() object called keystoreProperties.
    def keystoreProperties = new Properties()
    
    // Load your keystore.properties file into the keystoreProperties object.
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

    在build.gradle同时定义获取keystore配置文件信息。

  4. 记得在buildType中配置打包签名信息即可

    buildTypes {
            debug {
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.release
            }
            release {
                minifyEnabled true
                // Zipalign优化
                zipAlignEnabled true
                // 移除无用的resource文件
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.release
            }
        }

    以上定义好之后就可以在我们构建release包时自动为我们进行签名,如果有改动只需要改动配置文件即可。

1.3.2 管理依赖

在我们添加依赖的时候往往都很少采用动态获取最新版配置,因为我们需要在依赖中写明版本号,在多模块构建时如果有依赖版本的修改就会变得十分麻烦,其实我们可以通过在project build.gradle下面配置ext代码块,他是定义额外属性的一种方式。你可以使用属性来动态定制构建过程。例如:

ext{
    //版本号相关
    versionCode = 10
    versionName = '1.0.0'
    compileSdkVersion = 26
    minSdkVersion = 15
    targetSdkVersion = 26
}

我们可以这样配置一些共有的信息,然后在模块build.gradle中使用 

defaultConfig {
        applicationId "com.xxxx.xxxxxxx"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

1.3.3 自动修改生成APK名字

模板,可依照需求进行修改。

def appName(String outputFileName, String versionName) {
    String appName = outputFileName.replace("app-", "").replace(".apk", "");
    String debugName = appName.replace("-debug", "");
    if (appName == debugName) {// 定义release版本生成的apk的名字
        appName = appName.replace("-release", "") + visionName(versionName) + compilePackTime() + ".apk";
    } else { // 定义debug版本生成的apk的名字
        appName = debugName + visionName(versionName) + compilePackTimeD() + "_debug.apk";
    }
    return appName;
}

// 版本号转换
def visionName(String visionName) {
    return "_V" + visionName.replace(".", "") + "_";
}

// 当前时间
def compilePackTime() {
    return (new Date().format("yyyyMMdd HHmm", TimeZone.getTimeZone("GMT+8"))).replace(" ", "_")
    //   //
}

def compilePackTimeD() { //debug模式下name包含时分(秒)会出现 安装时错误:The APK file does not exist on disk
    return (new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+8")))
}
android{
    ......
   android.applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                outputFileName = appName(outputFile.name,             
             defaultConfig.versionName)
            } else {//这里使用之前定义apk文件名称
                outputFileName = 
"${variant.productFlavors[0].name}_${defaultConfig.versionName}_${compilePackTime()}_.apk"
            }
        }
    }
}

1.3.4 自定义BuildConfig,轻松切换测试和生产环境

一般来说app在未发布前,接口地址都是测试环境,而在发布时会切换到生产环境,我们有的时候需要经常修改代码来切换,这也可以通过修改gradle来做到。

android {
    buildTypes {
        debug {
            buildConfigField "String","API_URL",        "\"http://test.example.com/api\""
            buildConfigField "boolean","LOG_HTTP_CALLS","true"
        }
        release {
            buildConfigField "String","API_URL",            "\"http://example.com/api\""
            buildConfigField "boolean","LOG_HTTP_CALLS","false"
        }
    }
}

这样在代码中我们就可以通过BuildConfig类来引用API_URL直接做到修改url地址,注意地址需要有转义 

1.3.5 多渠道打包

    网络上很多资料需求不同,后续会有单独博客讲解

1.4 Gradle优化,加速构建

1.4.1加速构建参数

  1. 配置Gradle参数加速构建,在gradle.properties文件中设置一个属性来启动并行构建,org.gradle.parallel=true,另外一种方式是启动Gradle daemon开启一个守护进程。任何后续构建都将复用该守护进程,从而减少启动成本。只要你使用Gradle,该进程就一直存活,并且在空闲三个小时后终止。org.gradle.daemon=true。
  2. 调整JVM参数。

Gradle有一个叫作jvmargs的属性可以让你为JVM的内存分配池设置不同的值。对构建速度有直接影响的两个参数是Xms和Xmx。Xms参数用来设置初始内存大小,Xmx用来设置最大内存。你可以在gradle.properties文件中,手动设置这些值:org.gradle.jvmargs=-Xms256m -Xmx1024m

    3.多模块构建加速配置:org.gradle.confgureondemand,它会忽略正在执行的task不需要的模块来限制在配置阶段的时间消耗。

1.4.2 优化apk

  • 混淆:

    使用ProGuard,它可以缩减APK文件大小,还可以在编译期优化、混淆和预校验你的代码。在gradle中使用minifyEnabled的布尔类型属性,设置为true来激活ProGuard。激活之后我们可以通过配置proguard-rules.pro来保留一下我们不需要删除和混淆的类,网上对于这个文件的配置已经有很多模板这里不贴配置。

  • 缩减Apk大小:   

自动缩减:

    直接配置shrinkResources为true 

android {
       buildTypes {
           release {
                 minifyEnabled = true
                 shrinkResources = true
               }
        }
}

如果有动态资源被删除,可以在res/raw 下配置keep.xml,防止资源被删除。


手动缩减:

除去语言包以及一些密度图片,或者so不同架构适配文件。可以通过手动配置方法,

android {
       defaultConfig {
           resConfigs “en",“da",“nl"
       }
}

保留语言

android {
       defaultConfig {
           resConfigs “hdpi",“xhdpi",“xxhdpi",“xxxhdpi"
       }
}

保留图片

android{
    defaultConfig{
          ndk {
            abiFilters 'armeabi-v7a'//, 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
        }  
    }
}

so库

你可能感兴趣的:(Android技术,Android,Gradle)