Home

Awesome

Android-Flavors

概述

该项目演示了在 Android Studio 中使用 gradle 构建渠道包。已更新支持 Android Studio 3.x,Gradle 4.x。

渠道号

以友盟 SDK 为例,打包多渠道:GooglePlay,小米,友盟,360,豌豆荚,应用宝。 在 AndroidManifest.xml 中加入渠道区分标识。

<meta-data 
    android:name="UMENG_CHANNEL"
    android:value="${UMENG_CHANNEL_VALUE}" />

然后在 build.gradle(Module: app) 中加入渠道打包替换对应的 UMENG_CHANNEL_VALUE 代码。

// 渠道Flavors,配置不同的渠道
productFlavors {
    GooglePlay {}
    xiaomi {}
    umeng {}
    qihu360 {}
    wandoujia {}
    yingyongbao {}
    //其他...
}

// 批量配置渠道
productFlavors.all {
    flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}

自定义 apk 名字

我们可以指定不同渠道号生成的 apk 的名字,这样方便打包出来区别哪个 apk 是对应哪个渠道的。

如下命名格式为:渠道名-v版本号-打包时间.apk

//打包重命名
applicationVariants.all { variant ->
        if (variant.buildType.name == "release") {
            variant.outputs.all { output ->
                def fileName = output.outputFile.name
                if (fileName.endsWith(".apk")) {
                    def apkName = "${variant.productFlavors[0].name}-v${variant.versionName}-${releaseTime()}.apk";
                    outputFileName = apkName
                }
            }
        }
    }

渠道自定义

不同的渠道定义不同的 applicationId, versionCode, versionName

flavorDimensions "test","test1","test2"
//定义渠道
productFlavors {
    main_test {
        applicationId "com.jeanboy.app.flavors"
        versionCode rootProject.ext.mainTestVersionCode
        versionName rootProject.ext.mainTestVersionName
        //定义manifest中替换值,如:渠道号
        resValue("string", "test_app_id", "2017-8-14 12:09:35")
        //定义混淆文件
        proguardFiles getDefaultProguardFile('proguard-android.txt'), './src/main_test/proguard-rules.pro'
        dimension "test"
    }
    main_test1 {
        applicationId "com.jeanboy.app.flavorstest1"
        versionCode rootProject.ext.mainTest1VersionCode
        versionName rootProject.ext.mainTest1VersionName
        resValue("string", "test_app_id", "2017-8-14 12:09:35")
        proguardFiles getDefaultProguardFile('proguard-android.txt'), './src/main_test1/proguard-rules.pro'
        dimension "test1"
    }
    main_test2 {
        applicationId "com.jeanboy.app.flavorstest2"
        versionCode rootProject.ext.mainTest2VersionCode
        versionName rootProject.ext.mainTest2VersionName
        resValue("string", "test_app_id", "2017-8-14 12:09:35")
        proguardFiles getDefaultProguardFile('proguard-android.txt'), './src/main_test2/proguard-rules.pro'
        dimension "test2"
    }
}

不同渠道不同签名文件

定义渠道包签名文件

signingConfigs {
    test {
        storeFile file('../resources/test.jks')//密钥文件位置
        storePassword 'test123'//密钥密码
        keyAlias 'test'//密钥别名
        keyPassword 'test123'//别名密码
    }
    test1 {
        storeFile file('../resources/test1.jks')
        storePassword 'test123'
        keyAlias 'test'
        keyPassword 'test123'
    }
    test2 {
        storeFile file('../resources/test2.jks')
        storePassword 'test123'
        keyAlias 'test'
        keyPassword 'test123'
    }
}

指定不同渠道使用的签名文件

buildTypes {
    debug {
        minifyEnabled false
        shrinkResources false
        zipAlignEnabled true
        versionNameSuffix "-debug"//版本命名后缀
        buildConfigField "boolean", "LOG_DEBUG", "true"
        //定义debug时使用的签名文件
        signingConfig signingConfigs.test
        signingConfig signingConfigs.test1
        signingConfig signingConfigs.test2
    }

    release {
        minifyEnabled true//是否开启代码混淆
        shrinkResources true//移除无用的资源文件,依赖于minifyEnabled必须一起用
        multiDexEnabled true//解决65535
        zipAlignEnabled true//对齐zip
        debuggable false // 是否debug
        buildConfigField "boolean", "LOG_DEBUG", "false"
        signingConfig signingConfigs.test
        signingConfig signingConfigs.test1
        signingConfig signingConfigs.test2
    }
}

不同渠道不同资源文件

例如:不同渠道需要不同的应用名

|-app
    |-src
        |-main
        |   |-res
        |       |-values
        |           |-strings.xml
        |               |-<string name="app_name">Android-Flavors</string>
        |-main_test
        |   |-res
        |       |-values
        |           |-strings.xml
        |               |-<string name="app_name">Android-Flavors-test</string>
        |-main_test1
        |   |-res
        |       |-values
        |           |-strings.xml
        |               |-<string name="app_name">Android-Flavors-test1</string>
        |-main_test2
        |   |-res
        |       |-values
        |           |-strings.xml
        |               |-<string name="app_name">Android-Flavors-test2</string>

在 src 下创建与 main 同级的渠道目录,里面可创建与 main 目录下对应的目录或文件,打包时会以增量或覆盖的方式替换。

res 目录下的文件可以同名覆盖,java 或其他代码目录中类名不允许重复。

编译某个渠道包的时候遵循以下4条准则:

第三方 SDK

例如:test1 渠道中需要使用某个 SDK,而其他渠道不需要使用。

android {
    productFlavors {
        test1 {
        }
    }
}
...
dependencies {
    provided 'com.xxx.sdk:xxx:1.0'//提供 sdk
    test1Compile 'com.xxx.sdk:xxx:1.0'//指定 test1 渠道编译
}

接下来,需要在代码中使用反射技术判断应用程序是否添加了该SDK,从而决定是否要使用 SDK。部分代码如下:

class MyActivity extends Activity {
    private boolean useSdk;

    @override
    public void onCreate(Bundle savedInstanceState) {
        try {
            Class.forName("com.xxx.sdk.XXX");
            useSdk = true;
        } catch (ClassNotFoundException ignored) {

        }
    }
}

参考资料

美团Android自动化之旅—适配渠道包

Gradle App项目的多渠道打包实现

多渠道打包

关于我

如果对你有帮助,请 star 一下,然后 follow 我,给我增加一下分享动力,谢谢!

如果你有什么疑问或者问题,可以提交 issue 和 request,发邮件给我 jeanboy@foxmail.com

或者加入下面的 QQ 群来一起学习交流。

<a target="_blank" href="http://shang.qq.com/wpa/qunwpa?idkey=0b505511df9ead28ec678df4eeb7a1a8f994ea8b75f2c10412b57e667d81b50d"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="Android技术进阶:386463747" title="Android技术进阶:386463747"></a>

License

Copyright 2017 jeanboy

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.