Android组件化架构 - 9.Gradle优化

gradle本质是一个自动化构建工具, 使用基于groovy的特定领域语言来声明项目设置;

  1. 根目录的gradle文件
buildscript {//构建脚本引用
    ext {
        kotlin_version = '1.3.72'
    }
    repositories {//插件仓库配置
        google()
        jcenter()
        mavenCentral()
    }
    dependencies {//依赖插件
        //google的Android Gradle插件
        classpath 'com.android.tools.build:gradle:3.5.3'

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

        //美团walle多渠道打包插件
        classpath 'com.meituan.android.walle:plugin:1.1.6'
        //kotlin插件
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        //greendao插件
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
        //ARouter插件
        classpath "com.alibaba:arouter-register:1.0.2"
    }
}


allprojects {//全部项目的配置
    repositories {//全部项目引用的基础仓库配置
        google()
        jcenter()
        mavenCentral()
        maven { url 'http://developer.seedland.cc/nexus/repository/maven-dev/' }
        maven { url 'https://jitpack.io' }
    }
}

task clean(type: Delete) {//声明任务
    delete rootProject.buildDir//删除主路径buildDir文件夹
}

  1. app module的gradle文件:
//引入构建需要用到的gradle插件工具库
//每个build.gradle自身是一个Project对象,project.apply()会加载某个工具库到project对象中
//apply plugin:'xxx' 会将project对象传递入工具库,然后通过插件中的Groovy文件来操作
//project对象的属性, 以完善配置初始化信息
apply plugin: 'com.android.application'
apply plugin: 'walle'
apply plugin: 'kotlin-android'
apply plugin: 'org.greenrobot.greendao'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.alibaba.arouter'

//android{} ,dependencies{} 是函数方程式,使用闭包函数的编写方式, 
//相当于project.android(){} ,project.dependencies(){}
android {
    compileSdkVersion 30 //编译的sdk
    defaultConfig { //默认配置
        applicationId "com.ljy.publicdemo" //包名
        minSdkVersion 17 //最低支持版本
        targetSdkVersion 30 //支持的目标版本
        versionCode 1 //版本号
        versionName "1.0" //版本名
        multiDexEnabled true //dex分包支持
        //如果应用没有做国际化,可以让应用仅支持 中文的资源配置
        resConfigs "zh"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" //测试脚本
        ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64'
            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
        }


        //ARouter配置
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

    signingConfigs {//签名配置
        key {
            keyAlias 'key0'
            keyPassword '666666'
            storePassword '666666'
            storeFile file('./ljy_key.jks')
        }
    }

    lintOptions {
        // true--关闭lint报告的分析进度
        quiet true
        // true--错误发生后停止gradle构建
        abortOnError false
        // true--只报告error
        ignoreWarnings true
        // true--忽略有错误的文件的全/绝对路径(默认是true)
        //absolutePaths true
        // true--检查所有问题点,包含其他默认关闭项
        checkAllWarnings true
        // true--所有warning当做error
        warningsAsErrors true
        // 关闭指定问题检查
        disable 'TypographyFractions','TypographyQuotes'
        // 打开指定问题检查
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
        // 仅检查指定问题
        check 'NewApi', 'InlinedApi'
        // true--error输出文件不包含源码行号
        noLines true
        // true--显示错误的所有发生位置,不截取
        showAll true
        // 回退lint设置(默认规则)
        lintConfig file("default-lint.xml")
        // true--生成txt格式报告(默认false)
        textReport true
        // 重定向输出;可以是文件或'stdout'
        textOutput 'stdout'
        // true--生成XML格式报告
        xmlReport false
        // 指定xml报告文档(默认lint-results.xml)
        xmlOutput file("lint-report.xml")
        // true--生成HTML报告(带问题解释,源码位置,等)
        htmlReport true
        // html报告可选路径(构建器默认是lint-results.html )
        htmlOutput file("lint-report.html")
        //  true--所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
        checkReleaseBuilds true
        // 在发布版本编译时检查(即使不包含**重点内容**lint目标),指定问题的规则生成崩溃
        fatal 'NewApi', 'InlineApi'
        // 指定问题的规则生成错误
        error 'Wakelock', 'TextViewEdits'
        // 指定问题的规则生成警告
        warning 'ResourceAsColor'
        // 忽略指定问题的规则(同关闭检查)
        ignore 'TypographyQuotes'
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    flavorDimensions "jin"
    productFlavors {
        higher {
            applicationId "com.ljy.publicdemo"
            minSdkVersion 26
            buildConfigField 'boolean', 'isLite', "false"
//            resValue "string", "app_name_new", "JinDemo"
            manifestPlaceholders = [
                    app_icon    : "@mipmap/ic_launcher_normal",
                    channelName : "higher",
                    verNum      : "2",
                    app_name_new: "JinDemoHigher"
            ]
        }

        lower {
            applicationId "com.ljy.publicdemo.lite"
            minSdkVersion 17
            buildConfigField 'boolean', 'isLite', "true"
//            resValue "string", "app_name_new", "JinDemoLite"
            manifestPlaceholders = [
                    app_icon    : "@mipmap/ic_launcher_lite",
                    channelName : "lower",
                    verNum      : "1",
                    app_name_new: "JinDemoLower"
            ]
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            buildConfigField("boolean", "isDebug", "false")
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.key
            android.applicationVariants.all { variant ->
                variant.outputs.all {
                    outputFileName = "LjyPublicDemo_${variant.buildType.name}_${variant.versionName}.apk"
                }
            }
        }
        debug {
            minifyEnabled false
            buildConfigField("boolean", "isDebug", "true")
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.key
            android.applicationVariants.all { variant ->
                variant.outputs.all {
                    outputFileName = "LjyPublicDemo_${variant.buildType.name}_${variant.versionName}.apk"
                }
            }
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    // 配置数据库相关信息
    greendao {
        //数据库版本号
        schemaVersion 1
        // 设置DaoMaster、DaoSession、Dao 包名
        daoPackage 'com.ljy.publicdemo.greendao'
        //设置DaoMaster、DaoSession、Dao目录
        targetGenDir 'src/main/java'
    }

    //gradle的命名提示机制,让各module中资源名必须按一定前缀命名,有助于防止组件化多module时的资源冲突
    resourcePrefix 'app_'
}

walle {
    // 指定渠道包的输出路径
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // 定制渠道包的APK的文件名称
    apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
    // 渠道配置文件
    channelFile = new File("${project.getProjectDir()}/channel")
}

tasks.withType(JavaCompile) {
    //在 Gradle 4.10 版本之后便默认使用了增量编译
    //如果在更老的版本需要启动增量编译,可以使用如下配置:
    options.incremental = true
    //解决java控制台输出中文乱码
    options.encoding = "UTF-8"
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation project(path: ':mylibrary')
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation project(':library')   
}

gradle的生命周期分三个阶段:

1. 初始化: settings.gradle中声明module, 决定哪些工程会加入构建过程,并且创建project对象
include ':app', ':library',':datepicker',':heigher_setting',':lower_setting',':mylibrary'
2. 配置: 按引用树去执行所有工程的build.gradle脚本,配置project对象,一个project对象由多个任务组成
3. 构建: 运行阶段会根据gradle命令传递过来的task名称,执行相关依赖任务
  1. 版本参数优化

每个module的build.gradle文件都拥有一些必要的属性,同一个Android工程中,在不同的模块中要求一些属性一致,
如compileSdkVersion,如果引用不一致,属性不会被合并引入到工程中,这样一方面会造成资源的重复,包体积增大,另一方面会降低编译效率;
那么就需要有一个统一的,基础的gradle配置

  • 优化方案 1: 使用共同参数的方式进行配置
//创建一个 common_config.gradle文件
project.ext {
    compileSdkVersion = 30
    buildToolsVersion = "30.0.0"
    minSdkVersion = 17
    targetSdkVersion = 30
    applicationId = "com.ljy.publicdemo"
    versionCode = 1
    versionName ="1.0"
}

//在module的gradle首行引用common_config,并通过类似静态变量的方式引用属性
apply from: "${rootProject.rootDir}/common_config.gradle"//引用额外配置
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion project.ext.compileSdkVersion
    buildToolsVersion project.ext.buildToolsVersion

    defaultConfig {
        minSdkVersion project.ext.minSdkVersion
        targetSdkVersion project.ext.targetSdkVersion
        versionCode project.ext.versionCode
        versionName project.ext.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

  • 优化方案 2: 使用Android对象配置
//android{}中提供了android这个变量,那么可以进一步简化代码,在common_config.gradle的
//project.ext中添加一个闭包方法来指定project对象的变量
project.ext {
    compileSdkVersion = 30
    buildToolsVersion = "30.0.0"
    applicationId = "com.ljy.publicdemo"
    minSdkVersion = 17
    targetSdkVersion = 30
    versionCode = 1
    versionName = "1.0"
    setDefaultConfig = {
        extension -> //闭包参数extension相当于android对象
            extension.compileSdkVersion compileSdkVersion
            extension.buildToolsVersion buildToolsVersion
            extension.defaultConfig {
                applicationId applicationId
                minSdkVersion minSdkVersion
                targetSdkVersion targetSdkVersion
                versionCode versionCode
                versionName versionName

                testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
                consumerProguardFiles "consumer-rules.pro"
            }
            extension.dataBinding {
                enabled true
            }
    }
} 
//在bulid.gradle中就可以使用 setDefaultConfig了,类似属性设置,实际上是函数调用
apply from: "${rootProject.rootDir}/common_config.gradle"//引用额外配置
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    //使用闭包函数输入android这个对象到函数中
    project.ext.setDefaultConfig android

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
  • 优化方案 3: 使用project对象配置(最终版本)
//common_config.gradle:
project.ext {
    compileSdkVersion = 30
    buildToolsVersion = "30.0.0"
    applicationId = "com.ljy.publicdemo"
    minSdkVersion = 17
    targetSdkVersion = 30
    versionCode = 1
    versionName = "1.0"

    //设置app module 配置
    setAppDefaultConfig = {
        extension ->
            //引用application插件库
            extension.apply plugin: 'com.android.application'
            extension.description "app"
            setAndroidConfig extension.android
            setDependencies extension.dependencies
    }

    //设置lib module 配置
    setLibDefaultConfig = {
        extension ->
            //引用Lib插件库
            extension.apply plugin: 'com.android.library'
            extension.description "lib"
            setAndroidConfig extension.android
            setDependencies extension.dependencies
    }

    //设置android配置
    setAndroidConfig = {
        extension -> //闭包参数extension相当于android对象
            extension.compileSdkVersion compileSdkVersion
            extension.buildToolsVersion buildToolsVersion
            extension.defaultConfig {
                minSdkVersion minSdkVersion
                targetSdkVersion targetSdkVersion
                versionCode versionCode
                versionName versionName

                testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
                consumerProguardFiles "consumer-rules.pro"
                //ARouter配置
                javaCompileOptions {
                    annotationProcessorOptions {//路由每个模块的名称
                        arguments = [AROUTER_MODULE_NAME: extension.project.getName()]
                    }
                }
            }
            extension.dataBinding {
                enabled true
            }
    }

    //设置依赖
    setDependencies={
        extension ->
            extension.implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
            //每个module都需要应用路由的apt插件库才能生成相应代码,这里无需重复编写每个module的gradle
            extension.annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
    }
}

//build.gralde中只需要传入project对象到闭包函数中即可
  1. 调试优化

业务模块调试, 将单一模块作为app启动, 然后用于调试测试, 这样保证了单独模块可以分离调试;

1. 业务模块是library module, 需要将apply plugin: 'com.android.library' 
  变为 apply plugin: 'com.android.application',
  将模块作为application module才能引入app构建流程;
2. 每个application都需要配置applicationId
3. androidMainfest中需要配置默认启动的Activity
<activity android:name=".datepicker.DatePickerActivity"
    android:theme="@style/Theme.AppCompat">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
4. common_config中声明控制模块是否调试的变量
project.ext {
    compileSdkVersion = 30
    buildToolsVersion = "30.0.0"
    minSdkVersion = 17
    targetSdkVersion = 30
    versionCode = 1
    versionName = "1.0"

    //控制组件模块调试
    isDatePickerDebug=true
}
5. 在模块的build.config中将变量作为开关
apply from: "${rootProject.rootDir}/common_config.gradle"

if (project.isDatePickerDebug) {
    project.ext.setAppDefaultConfig project//设置app配置
} else {
    project.ext.setLibDefaultConfig project//设置lib配置
}

android{
    sourceSets {
        main {
            if (project.isDatePickerDebug) {
                manifest.srcFile 'src/debug/AndroidManifest.xml'
                res.srcDirs = ['src/debug/res', 'src/main/res']
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                resources {
                    exclude 'src/debug/*'
                }
            }
        }
    }
}

6. 原 app module 下,需要移除已经单独调试的模块的依赖
  if (!project.isDatePickerDebug) {
    implementation project(':datepicker')
  }
  1. 资源引用配置
    gradle有多种资源引用的方式

(1) 使用sourceSets指定文件路径

    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml' //AndroidManifest路径
            jniLibs.srcDirs = ['libs']//so库路径
            resources.srcDirs = ['src']//全部资源文件路径
            aidl.srcDirs = ['src']//aidl文件路径
            renderscript.srcDirs = ['src']//renderscript文件路径
            res.srcDirs = ['res']//res资源文件路径
            assets.srcDirs = ['assets']//asset资源文件路径
            
        }
    }

(2)可以动态添加res资源,在 buildTypes,productFlavors 中定义 resValue 变量,
resValue只能动态添加资源,不能替换,如果资源名重复,gradle会提示重复资源

resValue "string", "app_name_new", "JinDemo"

(3) 可以指定特定尺寸的资源,在 buildTypes, productFlavors 中定义 resConfigs

android {
    ...
    defaultConfig {
        ...
        resConfigs "zh","en"//剔除不需要的国际化
        resConfigs "hdpi","xhdpi","xxhdpi","xxxhdpi"//剔除自身,三方库,sdk中不需要的dpi资源
        ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters 'armeabi', 'armeabi-v7a' //, 'x86', 'arm64-v8a', 'x86_64', 'mips', 'mips64'
        }
    }
}

(4) productFlavors中,通过buildConfigField可以让代码直接读取到BuildConfig中的值,通过manifestPlaceholders配合AndroidManifest中的meta-data可以间接让代码读取到AndroidManifest的变量

  productFlavors {
        higher {
            buildConfigField 'boolean', 'isLite', "false"
            manifestPlaceholders = [
                    app_icon    : "@mipmap/ic_launcher_normal",
                    channelName : "higher",
                    verNum      : "2",
                    app_name_new: "JinDemoHigher"
            ]
        }
  }

Gradle加载优先级

在build.gradle中,有四个函数,优先级由高到低为:

buildTypes > productFlavors > Main > dependencies
我是今阳,如果想要进阶和了解更多的干货,欢迎关注公众号”今阳说“接收我的最新文章

推荐阅读更多精彩内容