一套代码打包多个项目这一篇就够了

最近在做项目制的开发,有一套开发好的产品原型的代码,然后根据不同的客户进行不同的定制开发。如果一个项目搞一个分支,要多开多个as,还有一个缺点就是如果产品原型的代码进行了变更和优化,所有的项目分支都要将产品修改后果的代码复制转移一次,麻烦!

build.gradle可以帮我们解决这个问题。

一.先来讲重点,配置变种:

1.在android { } 中增加如下代码,适用于as建立的项目,eclipse转换过来的项目需要增加sourceSets {}

    // 维度组合,productFlavors中不同种类的dimension可以组合,名字根据实际需要自己取
    flavorDimensions "mode", "channel"
    productFlavors {
        trunk {
            // 维度小组名称
            dimension "mode"
            // 修改项目包名,在清单文件中的包名后增加后缀;
            // 也可以使用applicationId "com.xx.xx" 替换掉默认的包名
            applicationIdSuffix ".trunk"
            // 编译后会自动生成在BuildConfig.java中,代码中通过使用BuildConfig.IPADRESS获取后面的值
            // 参数规则“String”表示参数类型,也可以是boolean等,"IPADRESS"是常量名称,最后一个是内容
            buildConfigField "String", "IPADRESS", "\"192.168.3.23:8080\""
            // 还可以增加各种不同的配置,defaultConfig中配置这里都可以用,重复的配置这里的会覆盖掉defaultConfig
        }
        guiyang {
            dimension "mode"
            applicationIdSuffix ".guiyang"
            buildConfigField "String", "IPADRESS", "\"192.168.8.47:8082\""
        }
        huawei{
            dimension "channel"
            // 给清单文件中的渠道占位符赋值
            manifestPlaceholders = [CHANNEL_VALUE:"huawei"]
        }
        xiaomi{
            dimension "channel"
            manifestPlaceholders = [CHANNEL_VALUE:"xiaomi"]
        }

2.在工程目录中新增如下目录

如果productFlavors中没有main:main为默认目录,每个组合都会含有main中的代码;

如果productFlavors中有main:main也相当于一个风味,不会和trunk合并(根据实际需要,trunk既可以是main的扩展也可以和main平级,完全看自己怎么配置)

trunk和guiyang(注意:文件夹名称要和productFlavors中的名字对应)中放各个定制项目中的差异代码,其下的目录结构文件非必须,如果需要修改main中的资源或新增代码,就在各自项目中增加对应的文件夹和文件,如果没有修改可以只有个主目录

trunk/guiyang和main中的文件合并规则如下:

图片、音频、 XML 类型的 Drawable ,layout等资源文件,将会进行文件级的覆盖
字符串、颜色值、整型等资源以及 AndroidManifest.xml ,将会进行元素级的覆盖
代码资源,同一个类, buildTypes 、 productFlavors 、 main 中只能存在一次,否则会有类重复的错误
覆盖等级为:buildTypes > productFlavors > main

3.配置后在as左下角Build Variants中可以看到如下组合,维度组合2*2,每个组合再分debug和release,共8种

选中哪一个就会编译哪一个,维度数量根据自己需要可以减少,也可以增加

4.依赖库也可以根据风味进行不同的依赖

编译用的第三方库。例如我们的app在某些情况下有地图功能,有些情况下没有,如果我们在代码里控制有没有这个功能那么它依赖的包还是会打入apk,这样就会造成大量无用的代码进入apk中。

现在不同的变种风味有不同的依赖,dependencies {}中可以这样写:

compile 'xxx'   // 所有的项目都依赖

trunkCompile 'yyy'  // 只有trunk项目依赖

trunkCompile 'zzz' // 只有trunk项目依赖

guiyangCompile 'yyy' // 只有guiyang项目依赖

implementation也可以这样用

5.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']
            jniLibs.srcDirs = ['libs']
        }

        trunk {
            java.srcDirs = ['src-trunk']
            res.srcDirs = ['res-trunk']
        }

        guiyang{
            java.srcDirs = ['src-guiyang']
            res.srcDirs = ['res-guiyang']
        }
}

sourceSets中主要指定代码目录,因为eclipse的目录结构和as不一样,as自动识别main下面的代码,需要通过sourceSets将eclipse的目录和as的目录进行对应

as中如果有特殊需要,也可以通过这种方式指定目录

二.重点讲完了,下面记一下build.gradle中各个配置的说明

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27

    // 默认配置,还有很多配置属性,参照gradle API
    // 相当于所有productFlavor的父类,自建productFlavor中没有的都使用这个里面的,有的就覆盖
    defaultConfig { 
        applicationId "com.example.xxx.gradledemo"
        minSdkVersion 14
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    // 签名的配置
    // productFlavor的属性,在所有的productFlavor都可以使用,一般用在buildTypes中,覆盖登记最高
    signingConfigs {
        realse { // 这个名字随意取,在buildTypes中通过signingConfigs. 调用
            storeFile file("gradle_demo.jks")
            storePassword "xxxxxx"
            keyAlias "xxx"
            keyPassword "xxxxxx"
        }
    }

    // 相当于一个维度dimension(隐藏有一个debug),和productFlavors中的风味进行组合
    buildTypes {
        // 也是一个productFlavor
        // 一般增加这个风味是为了不同的签名需要,还有很多配置属性,参照gradle API
        // release,debug中的buildConfigField会覆盖productFlavors中同名字的
        release {
            signingConfig signingConfigs.realse
            buildConfigField "boolean", "ISSHOW", "false"
            // 是否减小apk体积,打开混淆
            minifyEnabled true
            // 混淆文件的配置文件,getDefaultProguardFile('proguard-android.txt')是默认的,在SDK的tools/proguard目录下可以找到
            // proguard-rules.pro是自定义
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        
    }

    // 设置java的编译版本,通常是为了使用某些版本中的一些语言新特性
    // 一般不用设置
    /*compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }*/

    // 编译时加速,lint检查有错误时继续打包,不影响使用,最好是把lint错误全部修改
    // 还有很多配置属性,参照gradle API
    /*lintOptions {
        abortOnError false // 如果发现错误,lint是否停止打包
    }*/

    // 维度组合,productFlavors中不同种类的dimension可以组合,名字根据实际需要自己取
    flavorDimensions "mode", "channel"
    productFlavors {
        // 一个productFlavor
        trunk {
            // 维度小组名称
            dimension "mode"
            // 修改项目包名,在清单文件中的包名后增加后缀;
            // 也可以使用applicationId "com.xx.xx" 替换掉默认的包名
            applicationIdSuffix ".trunk"
            // 编译后会自动生成在BuildConfig.java中,代码中通过使用BuildConfig.IPADRESS获取后面的值
            // 参数规则“String”表示参数类型,也可以是boolean等,"IPADRESS"是常量名称,最后一个是内容
            buildConfigField "String", "IPADRESS", "\"192.168.3.23:8080\""
            // 还可以增加各种不同的配置,defaultConfig中配置这里都可以用,重复的配置这里的会覆盖掉defaultConfig
        }
        guiyang {
            dimension "mode"
            applicationIdSuffix ".guiyang"
            buildConfigField "String", "IPADRESS", "\"192.168.8.47:8082\""
        }
        huawei{
            dimension "channel"
            // 给清单文件中的渠道占位符赋值
            manifestPlaceholders = [CHANNEL_VALUE:"huawei"]
        }
        xiaomi{
            dimension "channel"
            manifestPlaceholders = [CHANNEL_VALUE:"xiaomi"]
        }
    }

    /*sourceSets {
        trunk {
            res.srcDirs = ['src/trunk/res']
            manifest.srcFile 'src/trunk/AndroidManifest.xml'
        }

        guiyang {
            res.srcDirs = ['src/guiyang/res']
            manifest.srcFile 'src/guiyang/AndroidManifest.xml'
        }
    }*/


}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
buildConfigField配置的数据在BuildConfig.java中,由as自动生成

在这个目录下面

三.build.gradle中的一些参数使用公用资源文件,主要是一些编译版本和jar包版本,modle和主工程都要用到的需要统一的

1.可以在根目录的build.gradle文件中增加ext{ },或者新建一个.gradle文件,然后引入到根目录的build.gradle

根目录下build.gradle 增加

ext {
    CompileSdkVersion = 27
    BuildToolsVersion = "27.0.3"
    MinSdkVersion = 14
    TargetSdkVersion = 27
}

主工程和modle的build.gradle中这样使用

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    defaultConfig {
        applicationId "com.example.xxx.gradledemo"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

或者在任意地方新建xxx.gradle,然后引入到根目录下build.gradle(在根目录中增加 apply from : 'xxx.gradle',需要根据xxx.gradle路径写)

ext{
    // 可以直接用
    /*compileSdkVersion = 27
    buildToolsVersion = "27.0.3"
    minSdkVersion = 14
    targetSdkVersion = 27*/

    // 也可以外面套一层
    android = [
            compileSdkVersion : 27,
            buildToolsVersion : "27.0.3",
            minSdkVersion : 14,
            targetSdkVersion : 27
    ]

    dependencies = [
            appcompatV7 : 'com.android.support:appcompat-v7:27.1.1',
            constraintVersion : 'com.android.support.constraint:constraint-layout:1.1.3'
    ]


}

主工程和modle的build.gradle中这样使用

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion

    defaultConfig {
        applicationId "com.example.zhangchi.gradledemo"
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation rootProject.ext.dependencies.appcompatV7
        implementation rootProject.ext.dependencies.constraintVersion
        testImplementation 'junit:junit:4.12'
    }
}

2.可以在gradle.properties中增加(一般放敏感信息,比如打包用的签名信息)

    在gradle.properties中添加敏感数据

#签名文件写实际路径
STOREFILE=../gradle_demo.jks  
STOREPASSWORD=xxxxxx
KEYALIAS=xxx
KEYPASSWORD=xxxxxx

主工程的build.gradle中这样使用

realse {
            storeFile file(STOREFILE)
            storePassword STOREPASSWORD
            keyAlias KEYALIAS
            keyPassword KEYPASSWORD
        }

四.gradle命令打包

1.Gradle配置(配不配置都行)

下载地址http://services.gradle.org/distributions/

下载你所需要的gradle版本,带all的,gradle-4.5-all.zip

下载后解压到你想要的目录

设置环境变量

新建系统变量:GRADLE_HOME,值为grdle路径

设置后测试是否配置成功

在as的Terminal工具栏中:

使用gradlew命令使用的是项目使用的gradle版本,使用gradle命令使用的是环境变量中配的gradle版本

多变种使用命令打包的命令:gradlew assembleGuiyangHuaweiRelease
gradle assemble维度组合名称(每个风味首字母大写)buildType下面的名字(首字母大写)

注:本篇博客纯属个人笔记,如果错误之处,还望帮忙指正,谢谢!

       如果想了解的更详细,可以读一本书籍《Android Gradle权威指南》

发布了12 篇原创文章 · 获赞 4 · 访问量 9606

猜你喜欢

转载自blog.csdn.net/lhy24680/article/details/91959539