Android官方多渠道方案详解

5,677 阅读6分钟

简介

实际应用开发中,不可避免的会接触到多渠道打包,不过其实大家常用的多渠道打包其实分为两种。第一:只是需要简单的渠道标识,然后通过标识代码里做一些必要的逻辑处理,这种情况现在网上有很多开源的方案,可以做到快速打包,这里就不在多做介绍了。第二:需要对代码、资源、依赖、配置等做到更深度的定制,比如为不同的应用市场设置不同的启动页和logo,这种情况就可以采用官方的ProductFlavors,下面也会详细介绍这种方案。
简单总结下这两种方案,第一种打包速度快,但是不够灵活,第二种有很强的定制性,但是由于每次回重新编译并签名所以在打包速度上慢很多,大家可以根据需求自由选择不同的方案,或者搭配使用。

方案介绍

构建配置

首先需要在module中的build.gradle配置你需要的渠道,渠道中可以修改一些defaultConfig中的配置

android {
    ···
    defaultConfig {
        minSdkVersion 19
        versionCode 1
        ...
    }
    
    // 渠道的维度,支持不同维度的渠道
    flavorDimensions "channel"
    productFlavors {
        common {
            dimension "channel"
        }
        xiaomi {
            minSdkVersion '21'
            versionCode 20000  + android.defaultConfig.versionCode
            versionNameSuffix "-minApi21"
            dimension "channel"
        }
        huawei {
            minSdkVersion '23'
            versionCode 20000  + android.defaultConfig.versionCode
            versionNameSuffix "-minApi23"
            dimension "channel"
        }
    }
    
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

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

Gradle 会通过上面的配置创建维度 * 维度中的渠道 * 构建类型数量的构建变体。在 Gradle 为对应构建变体的APK 命名时,首先是渠道,之后是构建类型。以上面的构建配置为例,Gradle 可以使用以下命名方案创建共6个构建变体:

构建变体:[common, xiaomi, huawei][debug, release]
对应 APK:app-[common, xiaomi, huawei]-[debug, release].apk

过滤变体

Gradle 会为每个可能的组合创建构建变体。都在Android Studio -> Build Variants中显示出来,不过某些特定的构建变体在您的项目环境中并不必要,也可能没有意义。您可以在build.gradle 文件中创建一个变体过滤器,以移除某些构建变体配置。

android {
    ···
    variantFilter { variant ->
        def names = variant.flavors*.name
        def buildTypeName = variant.buildType.name
        println (names + "==" + buildTypeName)
        // 这样就会移除 commonDebug的变体
        if (buildTypeName.contains("debug") && names.contains("common")) {
            setIgnore(true)
        }
    }
    ...
}

dependencies依赖

现实场景中有的时候不同的渠道,提供的功能也不尽相同,这样就需要对不同的渠道引入不同的组件包(前提App已经进行了组件拆分),如下简单配置就可以实现

configurations {
    // Gradle没有提供此细粒度级别的依赖方式,需要自己配置下不然会报错
    xiaomiDebugImplementation {}
}

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

    // 可以控制 xiaomi渠道下 的 debug 构建类型才去引入此包
    xiaomiDebugImplementation('com.xxx:xxx:1.6.0')
    debugImplementation('com.xxx:xxx:1.6.0')
    commonImplementation('com.xxx:xxx:1.6.0')
}

不同渠道的独立签名

同上面需求,对于功能不同的安装包,大概率是要独立的签名,通过简单的配置一样可以实现,不过对于debug的构建类型,是不支持定制签名的,具体原因未知...

signingConfigs {
    test11 {
        storeFile file("../test11.keystore")
        storePassword 'test11'
        keyAlias 'test11'
        keyPassword 'test11'
    }
    test22 {
        storeFile file("../test22.keystore")
        storePassword 'test22'
        keyAlias 'test22'
        keyPassword 'test22'
    }
}
// 渠道的维度,支持不同维度的渠道
flavorDimensions "channel"
productFlavors {
    common {
        dimension "channel"
    }
    xiaomi {
        dimension "channel"
    }
    huawei {
        dimension "channel"
    }
}
buildTypes {
    debug {
        //debug定制签名无效 只能指定一个或者使用默认的签名
//            productFlavors.huawei.signingConfig signingConfigs.test11
//            productFlavors.xiaomi.signingConfig signingConfigs.test22
//            productFlavors.common.signingConfig signingConfigs.test11
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

    }
    release {
        productFlavors.huawei.signingConfig signingConfigs.test11
        productFlavors.xiaomi.signingConfig signingConfigs.test22
        productFlavors.common.signingConfig signingConfigs.test11
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

Manifest配置

有时我们需要对Mainfest中的某个属性值做些调整,如配置不同渠道数据,App的Icon,还有替换声明Activity等等,都可以通过下面的配置实现,如果感觉这种简单的调整还不足以满足你的需求,可以看下方的定制源集的方案去深度的定制

// build.gradle
android {
    ···
    flavorDimensions "channel"
    productFlavors {
        common {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "Common Meta Data",
                                    "AppIcon"     : "@mipmap/ic_common",
                                    "MainActivity":CommonActivity"]
        }
        xiaomi {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "XiaoMi Meta Data",
                                    "AppIcon"     : "@mipmap/ic_launcher",
                                    "MainActivity":"XMActivity"]
        }
        huawei {
            dimension "channel"
            manifestPlaceholders = ["ChannelData" : "HuaWei Meta Data",
                                    "AppIcon"     : "@mipmap/ic_launcher",
                                    "MainActivity": "HWActivity"]
        }
    }
    ...
}

// Manifest 
<application
    //${AppIcon} 替换AppIcon
    android:icon="${AppIcon}"
    ... >

    //${ChannelData} 替换ChannelData
    <meta-data
        android:name="ChannelData"
        android:value="${ChannelData}"/>

    //${ChannelData} 替换声明Activity
    <activity android:name="${MainActivity}">
        ...
    </activity>
</application>



定制代码 资源 Manifest 等源集

有时候简单的调整可能不足以解决实际问题,这个时候可以直接定制源集解决问题,找到youModule\src,当前目录下有个main文件夹为我们工程的核心代码和资源,我们可以在同级下创建不同的渠道目录,如:common``xiaomi等,此目录可以放置自定义的java代码res资源AndroidManifestassets等。
不同变体目录(按优先级排列):

src/commonDebug/(构建变体源集)
src/debug/(buildTypes源集)
src/common/(productFlavors源集)
src/main/(主源集)

上面列出的顺序决定了在 Gradle 合并代码和资源时哪个源集具有较高的优先级。如果 commonDebug/debug/ 包含相同的文件,Gradle 将使用 commonDebug/ 源集中的文件。同样,Gradle 会为其他源集中的文件赋予比 main/ 中相同文件更高的优先级。Gradle 在应用以下构建规则时会考虑此优先级顺序:

  • 对于java/ 下的源代码只能有单一的类文件
    注:对于给定的渠道目录,如果找到两个或两个以上定义同一 Java 类的源集目录,Gradle 就会引发一个构建错误。例如,在构建调试 APK 时,您不能同时定义 src/common/Utility.javasrc/main/Utility.java。这是因为 Gradle 会在构建过程中检查这两个目录并引发duplicate class错误。如果针对不同的构建类型需要不同版本的 Utility.java,您可以让每个渠道定义其自己的文件版本,如:src/common/Utility.javasrc/xiaomi/Utility.java,而不将其包含在 main/ 中。
  • 所有Manifest合并为单个Manifest。将按照上述列表中的相同顺序指定优先级。也就是说,某个构建类型的Manifest设置会替换某个渠道的Manifest设置
  • 同样,values/ res/ 和 asset/ 目录中的如果存在有两个或两个以上的同名资源,比如在渠道中的资源将会替换main中资源,以下对于同时存在于strings.xml的同名资源和资源图标做个示例
// main 下的 图标资源
main\res\mipmap-hdpi\ic_launcher.png

// 在 xiaomi 下的 图标资源
xiaomi\res\mipmap-hdpi\ic_launcher.png 

//打包 xiaomi 渠道的时候会自动替换图片。
// main 下的 strings.xml
<resource>
    <string name="app_name">MultiChannel</string>
    <string name="string_merge">我是string,没被合并</string>
</resource>
// 在 xiaomi 下的 strings.xml 内容为:
<resource>
    <string name="string_merge">我是xiaomi,已经合并</string>
</resource>
//当打 xiaomi 渠道包时,最终 strings.xml 会变成:
<resource>
    <string name="app_name">MultiChannel</string>
    <string name="string_merge">我是xiaomi,已经合并</string>
</resource>

其他

命令构建

对于习惯于使用命令构建的同学来说有以下几点需要补充

  • 打全部包: gradle assemble
  • 打全部 Debug 包: gradle assembleDebug ,可以简写为 gradle aD 或 aDebug
  • 打全部 Release 包: gradle assembleRelease,可以简写为 gradle aR 或 aRelease
  • 打指定 flavor 包: gradle assemble(flavor)(Debug|Release) 如:gradle assembleXiaomiDebug
  • 打包完成后安装: gradle install(flavor)(Debug|Release)如:如:gradle installXiaomiDebug
  • 打包前先 clean 一下,在测试的时候很必要: gradle clean assembleXiaomiDebug

参考阅读

推荐:官方文档
android.jobbole.com/84752/
juejin.cn/post/684490…