利用Gradle工具构建变种包马甲包差异化包

需求

我们先看几个案例:

  1. 渠道A需要启动图a,但是渠道B上需要启动图b,渠道C...渠道D...;
  2. 渠道A的名称是StringA,渠道B的名称是StringB,渠道C...渠道D...;
  3. 一个页面的按钮点击跳转逻辑在渠道A和渠道B上需要定制。

以上的需求还可以自由组合穿插,反反复复的炒冷饭。手动修改或者切分支完成不是不可以,但是消耗大量的精力。

怎么办?我们可以借助gradle这个工具,帮我们完成这些差异化的工作。

gradle差异化构建变种

新建一个项目,看下app下的build.gradle文件:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.rjp.eaction"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support.constraint:constraint-layout:1.1.0'
        implementation 'com.android.support:support-annotations:27.1.1'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
}

这个时候还不支持变种包,那么现在我们来增加两个定制的渠道,需要用到productFlavors:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.rjp.eaction"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        flavorDimensions "versionCode"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    productFlavors {
        channel_A{
            applicationId "com.rjp.eaction.A"
        }

        channel_B{
            applicationId "com.rjp.eaction.B"
        }
    }

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support.constraint:constraint-layout:1.1.0'
        implementation 'com.android.support:support-annotations:27.1.1'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
}

我们增加了channel_A和channel_B两个渠道,而且都是只指定了applicationId,这里的applicationId可不是乱写的,而是gradle的预留字段,代表当前apk的包名。

打包

修改完之后怎么切换渠道呢?毕竟代码只有一份。我们点开Android Studio的侧边栏,有一个Build Variants选项,点开如下图:

gradle_build_variants.png

可以看到有四个选项。这里的选项个数是build.gradle里buildTypes个数和productFlavors个数的组合数。buildTypes下默认有一个debug,加上配置的release一共有2个;productFlavors下我们配置了2个,所以Build Variant我们一共有4种组合。

不信我们增加一个channel_C看下效果:

gradle_build_variants_C.png

同理,我们增加一个buildTypes也是同样的效果。

以上是开发阶段我们修改了变种渠道之后如何查看修改效果的方法。正式打包如何操作呢?

我们点开Android Studio右侧边栏的Gradle选项,找到当前项目的Tasks目录,点开other:

gradle_build_other.png

数一数,渠道A的debug包和release包,渠道B的debug包和release包,渠道C的debug包和release包。六个包,双击就可以产出。

(注意:正式包需要事先在gradle文件配置好签名,否则打出的都是未签名包)
(注意:使用build打包就不关心Build Variants下的配置了,两者一个开发阶段一个发包阶段,互不干扰)

测试

我们在MainActivity里面加一个TextView显示当前包的包名,看下修改是否生效。

我们分别双击assembleChannel_ADebug、assembleChannel_BDebug和assembleChannel_CDebug,找到app下的apk输出文件:

gradle_build_apk.png

可以看到,全部构建成功,如果你配置了正式包的签名,双击也能生成release文件夹。

接下来安装这三个apk到手机上,打开就能看到包名的差异了。

进阶

是不是只能修改applicationId呢?不是的。只要是defaultConfig下的参数都可以修改,而且productFlavors下的参数是会覆盖defaultConfig下的配置。

那么你可能觉得,好像用处不是很大?也不是的。只要是资源我们全部可以修改。

资源也就是res文件夹下面的所有内容都是可以差异化的,包括string、drawable和layout等等。

sourceSets

sourceSets非常强大,看名字,可以理解为指定资源的集合。

我们先在app下新建一个文件夹,名字随意,我们这里叫channels,然后在channels下新建三个文件夹分别放置A、B、C三个渠道的资源文件:

gradle_build_channels.png

这个res文件就是我们app下的src下的main下的res资源文件夹的一个扩展,但是这个res文件会覆盖app下的res的相同文件。比如我们当前这个strings.xml里面只是修改了app_name:

<resources>
    <string name="app_name">Channel_A</string>
</resources>

<resources>
    <string name="app_name">Channel_B</string>
</resources>

<resources>
    <string name="app_name">Channel_C</string>
</resources>

三个文件下app_name都不一样。这个文件是增加了,但是怎么使用呢:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.rjp.eaction"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        flavorDimensions "versionCode"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    productFlavors {
        channel_A {
            applicationId "com.rjp.eaction.A"
        }

        channel_B {
            applicationId "com.rjp.eaction.B"
        }

        channel_C {
            applicationId "com.rjp.eaction.C"
        }
    }

    sourceSets{
        channel_A {
            res.srcDirs = ['channels/channel_A/res']
        }

        channel_B {
            res.srcDirs = ['channels/channel_B/res']
        }

        channel_C {
            res.srcDirs = ['channels/channel_C/res']
        }
    }

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support.constraint:constraint-layout:1.1.0'
        implementation 'com.android.support:support-annotations:27.1.1'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
}

魔力就在这,我们在sourceSets下指定了渠道A的res的srcDirs文件夹位置,重新build项目,我们可以看到,channel_A下的res文件夹已经带上了资源的标识:

gradle_build_res.png

这个时候你能想到的能差异化的资源,都可以放到这个下面,什么color、图片、动画。项目在你手中仿佛是一个橡皮泥,你想它变啥样它就变啥样。

再进阶

是不是只有资源文件能够差异化呢?很明显不是的,我们的java文件也是可以差异化的,但是这个java文件的差异化比较笨,需要复制多份代码,但是对于需要切换分支付出的代价,这个拷贝代码的付出几乎可以忽略不计了。

同样我们需要先新建java代码文件夹:

gradle_build_java.png

sourceSets下只要稍加改动:

sourceSets{
        channel_A {
            res.srcDirs = ['channels/channel_A/res']
            java.srcDirs = ['channels/channel_A/java']
        }

        channel_B {
            res.srcDirs = ['channels/channel_B/res']
            java.srcDirs = ['channels/channel_B/java']
        }

        channel_C {
            res.srcDirs = ['channels/channel_C/res']
            java.srcDirs = ['channels/channel_C/java']
        }
    }

可以看到,我们同样指定了java代码的srcDirs扩展目录。这样我们就可以编写出差异化的界面了。我们现在有三个详情页,你可以随意修改代码,但是要注意一点,java目录下的包文件路径要保持一致,否则在切换build variants的时候仍然需要手动修改类的引用路径。

上面因为三个DetailActivity的路径一致,所以我们只需要在app下的AndroidManifest.xml注册就行,但是很多时候,我们的差异包下面单独有一个PayActivity需要注册,而其他的渠道根本用不到这个PayActivity的时候,这个PayActivity上哪注册呢?

没关系,AndroidManifest文件也是支持差异化的,再对sourceSets做一点改动:

sourceSets {
        channel_A {
            manifest.srcFile 'channels/channel_A/AndroidManifest.xml'
            res.srcDirs = ['channels/channel_A/res']
            java.srcDirs = ['channels/channel_A/java']
        }

        channel_B {
            res.srcDirs = ['channels/channel_B/res']
            java.srcDirs = ['channels/channel_B/java']
        }

        channel_C {
            res.srcDirs = ['channels/channel_C/res']
            java.srcDirs = ['channels/channel_C/java']
        }
    }

channel_A的注册文件内容:

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.rjp.eaction"
    >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        >
        <activity android:name=".PayActivity">
        </activity>
    </application>

</manifest>

这里我只对channel_A进行了修改,就是为了说明差异化,这些都不是必须要修改的。到这里,我们已经完全对app进行了差异化管理,包的目录结构也已经完整,最后看一眼我们的差异化目录结构:

gradle_build_payactivity.png

成果

最终打出三个debug包,我们整体看一遍效果。

debugA,首页包的信息,跳转详情页,详情页跳转支付页:

channel_A.gif

debugB,首页包的信息,跳转详情页,详情页左右结构:

channel_B.gif

debugC,首页包的信息,跳转详情页,详情页上下结构:

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

推荐阅读更多精彩内容