Gradle多渠道打包(动态设定App名称,应用图标,替换常量,更改包名,变更渠道)

36 篇文章 0 订阅

最近有个需求一次要打包多个类型的App,而且常量和String.xml都有变量。虽然之前也是一直存在变量,但是每次也仅仅只打包一个。这让我每次改变量,打包9个。要是以后每次都打包9次,我得疯了。
根据之前的了解,gradle 应该是可以解决这个问题的。所以就仔细研究了一番。

gradle.properties:

// build_tools版本号
ANDROID_BUILD_TOOLS_VERSION=22.0.1
// build_dk版本号
ANDROID_BUILD_SDK_VERSION=22

// version_name
VERSION_NAME=2.1
// version_code
VERSION_CODE=2100
// 包名
// PACKAGE= xxxx
// english version
PACKAGE= xxxx.en

//minSdkVersion
ANDROID_BUILD_MIN_SDK_VERSION=15
//targetSdkVersion
ANDROID_BUILD_TARGET_SDK_VERSION=22
// grade_version
GRADLE = com.android.tools.build:gradle:1.5.0

build.gradle(app 工程目录下):

//声明是Android程序,和library区别
apply plugin: 'com.android.application'
// 因为项目中使用了retrolambda
apply plugin: 'me.tatarka.retrolambda'

// 打包时间
def releaseTime() {
    return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
}

android {
//编译SDK的版本
    compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
    // build tools的版本
    buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION

    defaultConfig {
    //应用的包名,定义在gradle.properties
        applicationId project.PACKAGE
        minSdkVersion Integer.parseInt(ANDROID_BUILD_MIN_SDK_VERSION)
        targetSdkVersion Integer.parseInt(ANDROID_BUILD_TARGET_SDK_VERSION)
        versionCode Integer.parseInt(project.VERSION_CODE)
        versionName project.VERSION_NAME

        // dex突破65535的限制,为大程序而设
        multiDexEnabled true
        // AndroidManifest.xml 里面CHANNEL的value为 ${CHANNEL_VALUE}
        manifestPlaceholders = [CHANNEL_VALUE: "name"]
    }

    //执行lint检查,有任何的错误或者警告提示,都会终止构建,我们可以将其关掉。
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
        // 防止在发布的时候出现因MissingTranslation导致Build Failed!
        disable 'MissingTranslation'
    }

    dexOptions {
        incremental true
        javaMaxHeapSize "8g"
        jumboMode = true
        preDexLibraries = false
        threadCount ="8"
    }

    //签名
    signingConfigs {
        debug {
        }
        relealse {
            storeFile file('xxx.keystore')
            storePassword '***'
            keyAlias 'xxx'
            keyPassword '***'
        }
    }
    // 打包
    buildTypes {
    //debug  版本
//        debug {
//            buildConfigField "int", "ENV", "0"
//            minifyEnabled true
//            zipAlignEnabled true
//            shrinkResources true
//            proguardFile 'proguard-rules.pro'
//            signingConfig signingConfigs.relealse
//        }
//        pre {
//            buildConfigField "int", "ENV", "1"
//            minifyEnabled false
//            zipAlignEnabled false
//            shrinkResources false
//            signingConfig signingConfigs.relealse
//        }
//        online {
//            buildConfigField "int", "ENV", "2"
//            minifyEnabled false
//            zipAlignEnabled false
//            shrinkResources false
//            signingConfig signingConfigs.relealse
//        }
        release {
        // 定义ENV变量,可以通过BuildConfig.ENV 来获取,进行一些接口环境变化的操作
            buildConfigField "int", "ENV", "2"
            //混淆
            minifyEnabled true
            //Zipalign优化
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources true
            //混淆配置文件
            proguardFile 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.relealse
        }
    }

    //渠道Flavors
    productFlavors {
        Market_Google_Play {}
//        Market_xiaomi {}
//        Market_wandoujia {}
//        Market_Default {}
//        Market_Amazon {}
//        Market_yingyongbao {}
//        Market_360 {}
//        Market_baidu {}
    }

    // Java版本
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

// 替换manifest中定义的占位符
    productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
    }

    // apk输出重命名
    applicationVariants.all { variant ->
        variant.outputs.each { output ->

            // oldName
            def outputFile = output.outputFile

            if (outputFile != null && outputFile.name.endsWith('.apk')){

                // 获取中英文版本
                def isEn = variant.applicationId.endsWith('.en')
                def newName = ''
                def version= "-v${defaultConfig.versionName}"
                def time = "-${releaseTime()}.apk"
                def enName = 'appEN'
                def cnName = 'appCN'

                // release版本
//                if(variant.buildType.name.equals('release')) {
//
//                    // 获取渠道号
//                    def productFlavor = variant.productFlavors[0].name
//
//                    if (isEn) {
//                        newName = enName + version + '-' + productFlavor + time
//
//                    } else {
//                        newName = cnName + version + '-' + productFlavor + time
//                    }
//                }else if (variant.buildType.name.equals('pre')){
//                    if (isEn) {
//                        newName = enName + version + '-pre-online' + time
//
//                    } else {
//                        newName = cnName + version + '-pre-online'  + time
//                    }
//                }else if (variant.buildType.name.equals('online')){
//                    if (isEn) {
//                        newName = enName + version + '-online'  + time
//
//                    } else {
//                        newName = cnName + version + '-online'  + time
//                    }
//                }else{
                    if (isEn) {
                        newName = enName + version + '-debug'  + time

                    } else {
                        newName = cnName + version + '-debug'  + time
                    }
//                }
                output.outputFile = new File(outputFile.parent, newName)
            }
        }
    }
}

retrolambda {
    javaVersion JavaVersion.VERSION_1_7
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
    compile 'com.android.support:recyclerview-v7:22.2.1'
    compile 'com.annimon:stream:1.0.1'
    compile project(':mylibrary')
}
  • 最后会在app/build/outputs/apk下生成相应的文件 

debug:name-version-debug-time.apk 
release: name-version-productFlavors-time.apk

AndroidManifest.xml

<application>
<meta-data
            android:name="UMENG_APPKEY"
            android:value="54d32a3afd98c511cf000629" />
        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${CHANNEL_VALUE}" />

    </application>


先放一个完整的 多渠道/多环境 打包的配置,然后再来讲解。

实现了:

  1. 不同环境,不同包名;
  2. 不同环境,修改不同的 string.xml 资源文件;
  3. 不同环境,修改指定的常量;
  4. 不同环境,修改 AndroidManifest.xml 里渠道变量;
  5. 不同环境,引用不同的 module。

先放一个完整的配置,可以参考:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion '22.0.1'

    // 签名文件
    signingConfigs {
        config {
            keyAlias 'lyl'
            keyPassword '123456'
            storeFile file('../lyl.jks')
            storePassword '123456'
        }
    }

    // 默认配置
    defaultConfig {
        //applicationId "com.lyl.app"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0.0"

        signingConfig signingConfigs.config
        multiDexEnabled true
    }

    // 多渠道/多环境 的不同配置
    productFlavors {
        dev {
            // 每个环境包名不同
            applicationId "com.lyl.dev"
            // 动态添加 string.xml 字段;
            // 注意,这里是添加,在 string.xml 不能有这个字段,会重名!!!
            resValue "string", "app_name", "dev_myapp"
            resValue "bool", "isrRank", 'false'
            // 动态修改 常量 字段
            buildConfigField "String", "ENVIRONMENT", '"dev"'
            // 修改 AndroidManifest.xml 里渠道变量
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "dev"]
        }
        stage {
            applicationId "com.lyl.stage"

            resValue "string", "app_name", "stage_myapp"
            resValue "bool", "isrRank", 'true'

            buildConfigField "String", "ENVIRONMENT", '"stage"'

            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "stage"]
        }
        prod {
            applicationId "com.lyl.prod"

            resValue "string", "app_name", "myapp"
            resValue "bool", "isrRank", 'true'

            buildConfigField "String", "ENVIRONMENT", '"prod"'

            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "prod"]
        }
    }

    dexOptions {
        incremental true
        // javaMaxHeapSize "4g"
    }

    //移除lint检测的error
    lintOptions {
        abortOnError false
    }

    def releaseTime() {
        return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.config
        }

        release {
            buildConfigField("boolean", "LOG_DEBUG", "false")
            minifyEnabled false
            zipAlignEnabled true
            //移除无用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config

            // 批量打包
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        //输出apk名称为:渠道名_版本名_时间.apk
                        def fileName = "${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.facebook.android:facebook-android-sdk:4.0.0'
    compile project(':qrscan')
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.google.code.gson:gson:2.3'
    compile files('libs/android-async-http-1.4.6.jar')
    compile 'com.google.android.gms:play-services:7.5.0'
    compile 'com.android.support:support-annotations:22.1.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'de.hdodenhof:circleimageview:2.1.0'
}

接下来我们来详细看看修改特定的字段。

不同环境的设置基本都是在 productFlavors 里设置的,
而且在里面你想添加多少个环境都可以。

1. 不同环境,不同包名;
productFlavors {
    dev {
        applicationId "com.lyl.dev"
    }

    stage {
        applicationId "com.lyl.stage"
    }

    prod {
        applicationId "com.lyl.prod"
    }
}

这里注意,在 defaultConfig 中,大家应该都是写了个默认的 applicationId 的。
经测试,productFlavors 设置的不同环境包名会覆盖 defaultConfig 里面的设置,
所以我们可以推测,它执行的顺序应该是先执行默认的,然后在执行分渠道的,如果冲突,会覆盖处理,这也很符合逻辑。

<br />

2. 不同环境,添加 string.xml 资源文件;

利用 resValue 来定义资源的值,顾名思义 res 底下的内容应该都可以创建,最后用 R.xxx.xxx 来引用。
如下就根据不同的类型,添加了不同的 app_name 字段,以及定义了 布尔值,可以通过 R.string.app_name 来引用。

注意,这里是添加,是在 string.xml 里面添加了一个字段app_name,所以在现有的 string.xml 中不能有这个字段,否则会报错!!!
productFlavors {
    dev {
        resValue "string", "app_name", "dev_myapp"
        resValue "bool", "isrRank", 'false'
    }
    stage {
        resValue "string", "app_name", "stage_myapp"
        resValue "bool", "isrRank", 'true'
    }
    prod {
        resValue "string", "app_name", "myapp"
        resValue "bool", "isrRank", 'true'
    }
}

通过以上我们大概可以推测出 color、dimen 也可以通过类似的方法添加。

<br />

3. 不同环境,动态修改指定的常量;

使用 BuildConfig 的变量。

①定义字段

当我们定义如下字段之后,编译后自动生成文件,在 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 目录,
打开这个文件,我们就能看到我们所定义的字段了。

productFlavors {
    dev {
        buildConfigField "String", "ENVIRONMENT", '"dev"'
    }
    stage {
        buildConfigField "String", "ENVIRONMENT", '"stage"'
    }
    prod {
        buildConfigField "String", "ENVIRONMENT", '"prod"'
    }
}
②引用字段

在我们自己的任意的类中,来直接通过 BuildConfig 就可以调用我们定义的字段。

public class Constants {
    public static final String ENVIRONMENT = BuildConfig.ENVIRONMENT;

}

注意:这里有个小细节,看其中第三个参数,是先用了“'”,然后在用了“"”,这种语法在 Java 里可能比较陌生,但是在很多其他语言中,这种用法是很常见的。
它的意思是 "dev" 这个整体是属于一个字符串,至于为什么要这么写,你把单引号去掉,然后去 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 这个文件看一看就知道了。

<br />

4. 不同环境,修改 AndroidManifest.xml 里渠道变量
①在 AndroidManifest.xml 里添加渠道变量
<application
    android:icon="${app_icon}"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    ...
    <meta-data
        android:name="UMENG_CHANNEL"
        android:value="${ENVIRONMENT}" />
    ...
</application>
②在 build.gradle 设置 productFlavors
productFlavors {
    dev {
        manifestPlaceholders = [ENVIRONMENT: "dev",
                                app_icon   : "@drawable/icon_dev"]
    }
    stage {
        manifestPlaceholders = [ENVIRONMENT: "stage",
                                app_icon   : "@drawable/icon_stage"]
    }
    prod {
        manifestPlaceholders = [ENVIRONMENT: "prod",
                                app_icon   : "@drawable/icon_prod"]
    }
}

这样我们可以在不同环境使用不同的 key 值。

<br />

5. 不同环境,引用不同的 module

这个就很强大了,根据不同的环境,引用对应的 module。
你可以替换大量的图片,string,color,vaule等等。

首先,要建立跟渠道对应的 module,然后再引用。
引用方式如下:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    // 引用本的项目
    devCompile project(':devModule')
    stageCompile project(':stageModule')
    prodCompile project(':prodModule')

    // 也可以分渠道引用网络的。因为这里都相同,所以地址也就都一样了
    devCompile 'com.roughike:bottom-bar:2.0.2'
    stageCompile 'com.roughike:bottom-bar:2.0.2'
    prodCompile 'com.roughike:bottom-bar:2.0.2'
}

xxxCompile 代表 各个渠道的名称。
然后把需要分渠道的文件,放到不同的 module 里面,把主项目的文件删掉。
千万注意:如果这样做了,每次需要引用的时候,在各个渠道的 module 里面都必须要放置文件哦,不然会找不到资源。
通过这种方式可以替换整套素材资源,具体如何使用还得看项目需求。

<br />
<br />
通过以上方式,我们基本可以 通过 gradle 动态设定应用标题,应用图标,替换常量,设置不同包名,更改渠道等等。


打包编译

最后,做完所有的配置之后,然后就是打包操作了。

打包某一个(日常编译)

因为 buildTypes 里面有两种,所以每个渠道都会有两种模式。

打包所有的,就是正常打包流程

如图所示:

图片来源网络
图片来源网络

<br />
打包完成之后,然后就可以在我们指定的目录下,看到我们所生成的apk包。


使用 local.properties 存放私密配置

以上就可以基本实现 gradle 的设置,但是如果我们要将我们的项目上传到 Github ,或者要将项目发送给别人。上面有些私密的东西就会被别人看到。比如:.jks 文件的密码。
在项目跟目录下,有个 local.properties 文件,我们可以使用它来存放一些私密的属性,然后在 gradle 中读取,而 local.properties 文件不需要上传。
local.properties 文件里设置如下:

sdk.dir=D\:\\Android\\android-sdk

gaodeKey=e348025dd034d1c347dd0345e34802

keyPassword=123456
在 build.gradle 读取 local.properties 字段信息
// 加载 local.properties 资源
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() ;
properties.load( inputStream )

android {
    ...

    // 签名文件
    signingConfigs {
        config {
            keyAlias 'lyl'
            // 获取 local.properties 字段信息
            keyPassword properties.getProperty( 'keyPassword' )
            storeFile file('../lyl.jks')
            storePassword properties.getProperty( 'keyPassword' )
        }
    }

    ...
}

这样就可以将自己想要隐藏的一些数据隐藏起来。


可能加快 Android Studio 编译的办法
1. 在根目录的 build.gradle 里加上如下代码:
allprojects {
    // 加上这个
    tasks.withType(JavaCompile) {
        //使在一个单独的守护进程编译
        options.fork = true
        //增量编译
        options.incremental = true
    }

    repositories {
        jcenter()
    }
}
2. 在 app 级别下 build.gradle 里 加上
android {
    dexOptions {
        incremental true
    }
}

最后放上一个多渠道的项目地址,可以参考:
https://github.com/Wing-Li/boon



链接:https://www.jianshu.com/p/533240d222d3

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值