我爱编程

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

2018-05-28  本文已影响240人  刀放下好好说话

需求

我们先看几个案例:

  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
上一篇下一篇

猜你喜欢

热点阅读