Android 多渠道打包最佳实践

Android-Flavors

概述

该项目演示了在 Android Studio 中使用 gradle 构建渠道包。

渠道号

以友盟 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.each { output ->
            def filePath = output.outputFile.parent + "/${variant.buildType.name}"
            def fileName = output.outputFile.name
            if (fileName.endsWith(".apk")) {
                def apkName = "${variant.productFlavors[0].name}-v${variant.versionName}-${releaseTime()}.apk";
                output.outputFile = new File(filePath, apkName)
            }
        }
    }
}

渠道自定义

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

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'
    }
    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'
    }
    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'
    }
}

不同渠道不同签名文件

定义渠道包签名文件

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条准则:

  • 所有的源码(src/*/java)会用来共同编译生成一个 Apk,不允许覆盖,会提示 duplicate class found
  • 所有的 Manifests 都将会合并,这样一来就允许渠道包中可以定义不同的组件与权限,具体可参考官方 Manifest Merger
  • 渠道中的资源会以覆盖或增量的形式与 main 合并,优先级为 Build Type > Product Flavor > Main sourceSet
  • 每个 Build Variant 都会生成自己的R文件

第三方 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项目的多渠道打包实现

多渠道打包

项目地址:https://github.com/jeanboydev/Android-Flavors

我的公众号

欢迎你「扫一扫」下面的二维码,关注我的公众号,可以接受最新的文章推送,有丰厚的抽奖活动和福利等着你哦!?

如果你有什么疑问或者问题,可以 点击这里 提交 issue,也可以发邮件给我 jeanboy@foxmail.com

同时欢迎你 Android技术进阶:386463747 来一起交流学习,群里有很多大牛和学习资料,相信一定能帮助到你!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值