Android 多渠道打包实操(更改包名、图标、主题资源 、替换常量、第三方SDK Appkey配置)

最近在做一个通用版的会员系统,给予不同的公司使用,先前是通过切换版本分支来管理的,后面发现实在是繁琐和痛苦管理,仅仅是需要更改不同的常量、主题资源、包名、图标等等,主体代码逻辑功能基本不变。

先前了解过多渠道包的使用,其实这里完全可以通过 Gradle 的多渠道打包来这个痛点,期间也踩了坑,在这里做个记录

目录

一、通过 productFlavors 配置不同的渠道/环境

二、manifestPlaceholders 占位符使用

三、了解 ApplicationId 与 PackageName的区别

四、替换资源文件

五、打包和调试编译安装不同版本的渠道

以下为完整的实际项目配置这有两个渠道等同于给两家不同的公司会员 app 使用的配置

apply plugin: 'com.android.application'
def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
//加载本地文件
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.ablegenius.member"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 101
        versionName "1.0.101"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        multiDexEnabled true

        ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "arm64-v8a", "x86_64"
            //, 'mips', 'mips64'
        }

        // 渠道配置 gradle 3.0.0 以上需要有这个
        flavorDimensions "app"
    }

    signingConfigs {

        AblegeniusMemberConfig {
            //第一种:使用gradle直接签名打包
            /*  keyAlias 'dongwang'
            keyPassword '123123'
            storeFile file('src/main/WineverzhudiStoreFile.jks')
            storePassword '123123'*/

            //第二种:为了保护签名文件,把它放在local.properties中并在版本库中排除
            // ,不把这些信息写入到版本库中(注意,此种方式签名文件中不能有中文)

            storeFile file(properties.getProperty("keystroe_storeFile"))
            storePassword properties.getProperty("keystroe_storePassword")
            keyAlias properties.getProperty("keystroe_keyAlias")
            keyPassword properties.getProperty("keystroe_keyPassword")

            v2SigningEnabled false
        }

    }

    // 多渠道/多环境 的不同配置
    productFlavors {


        SatayKing {
            //此处的常量都会通过Gradle 在 BuildConfig.java 文件中生成 , 你可以直接在Class中使用 BuildConfig.XXXX 进行使用
            // 每个环境包名不同
            applicationId "com.ablegenius.member.satayking"
            // 动态添加 string.xml 字段;
            // 注意,如果在这添加,在 string.xml 不能有这个字段,会重名!!!这里使用资源文件覆盖的方式来处理应用名称
//            resValue "string", "app_name", "沙嗲王會員x"
            resValue "bool", "auto_updates", 'false'
            // 动态修改 常量 字段
            buildConfigField "String", "MAIN_H5_URL", '"https://xxxxxxx22/index.html"'
            //服務器請求地址
            buildConfigField "String", "SERVER_URL", '"https://cloudxxxx22/a"'
           //一些常量
            buildConfigField "String", "company", '"SatayKing"'
            buildConfigField "String", "serial", '"xxxxx"'
            buildConfigField "int", "ENVIRONMENTInt", '2'
            // 修改 AndroidManifest.xml 里渠道变量
            manifestPlaceholders = [CHANNEL_VALUE: "SatayKing"
                                    , app_icon   : "@mipmap/ic_launcher_shadiewang",
                                    //此方式可直接在 manifest 中通过 ${icon} 进行占位引用; 或者在main同级中创建不同渠道后创建 res 资源文件
                                    icon         : "@mipmap/ic_launcher_shadiewang",
                                    //极光相关
                                    JPUSH_PKGNAME: applicationId,
                                    JPUSH_APPKEY : "xxxxxxx", //JPush上注册的包名对应的appkey.
                                    JPUSH_CHANNEL: "developer-default", //暂时填写默认值即可.
                                    //Google Map 相关
                                    GoogleMapKey : "AIzaSyCLJ9Gng-xxxxx",

            ]

        }

        WineverHK {

            dimension "app"

            applicationId "com.ablegenius.member.wineverzhudi"

//            resValue "string", "app_name", "築地日本料理"
            resValue "bool", "auto_updates", 'true'
            resValue "drawable", "isrRank", 'true'
         
            buildConfigField "String", "MAIN_H5_URL", '"http://xxxxindex.html"'
            buildConfigField "String", "SERVER_URL", '"http://cloud.xxxx/a"'

            buildConfigField "String", "company", '"WineverHK"'
            buildConfigField "String", "serial", '"xxxx"'


            manifestPlaceholders = [CHANNEL_VALUE: "WineverHK"
                                    , app_icon   : "@mipmap/ic_launcher_zhudi",
                                    icon         : "@mipmap/ic_launcher_zhudi",

                                    JPUSH_PKGNAME: applicationId,
                                    JPUSH_APPKEY : "247aef555a20e8836d1ac361", //JPush上注册的包名对应的appkey.
                                    JPUSH_CHANNEL: "developer-default", //暂时填写默认值即可.

                                    GoogleMapKey : "AIzaSyCtAVjIVmGdnP44W2Nk8DjCT_OJISYUVxA",
            ]
        }


    }

    buildTypes {
        release {
            // release模式下,不显示log
            buildConfigField("boolean", "LOG_DEBUG", "false")
            // 为版本名添加后缀
//            versionNameSuffix "-relase"
            // 不开启混淆
            minifyEnabled false
            // 移除无用的resource文件
            shrinkResources false
            // 开启ZipAlign优化
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            signingConfig signingConfigs.AblegeniusMemberConfig


        }

        debug {

            // debug模式下,显示log
            buildConfigField("boolean", "LOG_DEBUG", "true")

            //为已经存在的applicationId添加后缀
//            applicationIdSuffix ".debug"
            // 为版本名添加后缀
            versionNameSuffix "-debug"
            // 不开启混淆
            minifyEnabled false
            // 不开启ZipAlign优化
            zipAlignEnabled false
            // 不移除无用的resource文件
            shrinkResources false

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.AblegeniusMemberConfig
        }
    }

    // 3.0 gradle 批量打包
    android.applicationVariants.all { variant ->
        variant.outputs.all {
            //输出apk名称为:渠道名_版本名_时间.apk
            outputFileName = "${variant.productFlavors[0].name}Member_v${defaultConfig.versionName}_${releaseTime()}.apk"
        }
    }

    sourceSets {
        SatayKing { res.srcDirs = ['src/SatayKing/res', 'src/SatayKing/res/'] }
        WineverHK { res.srcDirs = ['src/WineverHK/res', 'src/WineverHK/res/'] }
        main { res.srcDirs = ['src/main/res', 'src/main/res/'] }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation xxxx
}

一、通过 productFlavors 配置不同的渠道/环境

productFlavors {
    SatayKing {
        applicationId "com.ablegenius.member.satayking"
    }

    WineverHK {
        applicationId "com.ablegenius.member.wineverzhudi"
    }
}

 

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

二、manifestPlaceholders 占位符使用

项目中使用到了极光、GoogleMap 等第三方SDK的配置,大家都知道极光推送需要根据不同的包名 JPush上注册的包名对应的appkey 的才能进行推送,如何去修改呢?

使用 manifestPlaceholders 来 定义 【GoogleMapKey 】常量,
在 AndroidManifest.xml 中 使用 "${GoogleMapKey}" 来占位,

<application
android:icon="${icon}"
      android:label="${app_name}"
      xxxxx>
      <!--渠道配置-->
      <meta-data
          android:name="CHANNEL"
          android:value="${CHANNEL_VALUE}" />


      <!-- Google Map Key -->   
      <meta-data
          android:name="com.google.android.geo.API_KEY"
          android:value="${GoogleMapKey}" /> 

<!--  极光推送-->
    
      <!-- User defined. 用户自定义的广播接收器-->
      <receiver
          android:name="com.ablegenius.member.receiver.JpushReceiver"
          android:enabled="true">
          <!--android:process=":remote"广播运行在远端单独进程中 ,调试断点无法执行需要关闭 或者 debug时候选择 remote ! -->
          <intent-filter>
            
            xxxxx
              <!--推送包名必须一致使用Gradle中的常量才是最终的 -->
              <category android:name="${applicationId}" />
          </intent-filter>
      </receiver>

</application>

此处的app名称和图标都可以使用占位符的方式进行引用,

Tpis:如果是这种方式修改应用名称,注意应用名称定义在外层,通过 resValue 定义的常量String 需要 先使用 单引号 里面再是字符串,'"应用名称"'
            resValue "string", "app_name", "築地日本料理"

三、了解 ApplicationId 与 PackageName的区别

调试和打包出来的名称会以Gradle 中的 applicationId 为最终包名,在 Manifest中的并不是最终的会被修改,地图在做key验证的时候填写的包名应该是ApplicationId ,而不是packageName

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.ablegenius.member">     

四、替换资源文件

每个应用资源布局 主题样式,启动页图标、应用名称可能 不一样,这时怎么做呢? Google 做法:

在 main 的同级目录下创建以渠道名命名的文件夹,然后创建资源文件(路径要与 main 中的一致),然后打包的时候 gradle 就会自己替换或者合并资源。 替换图片和合并颜色的原理也相似。必须名称统一使用!

在对应的渠道文件夹中创建res 文件, 注意渠道文件夹 目录为main 同级中, 创建 res为 : src/渠道名称/res

image.png

五、打包和调试编译安装不同版本的渠道

选取不同的渠道,Gradle 会自动编译指定渠道,然后再运行项目即可

image.png

多渠道打包后很多渠道时 需要默认 安装哪个渠道, 需要 在Build 中做切换

也可以通过命令打包: ./gradlew assembleRelease

最后如果你有涉及到第三方的Appkey之类的一定要检查好这块,以及配置的SHA1值等

参考:

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

Gradle 实现 Android 多渠道定制化打包

ApplicationId 与 PackageName的区别

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容