【Gradle】构建衍生版本

叨逼叨

公司产品线一直是有两个产品,大概是一个客户版,一个商户版。
两个产品一直是分开维护的,因为从外包接过来他就是这样,大家也都没觉得有啥不妥。
直到有那么一天,老板在周一晨会上说,“嗯,商户版咋那么丑,改了吧”。
o(゚Д゚)っ啥!o(>﹏<)o不要啊!

商户版跟客户版功能其实差不多,有很多公用的界面,加上少许的特有界面。由于一些历史遗留问题,最后得到的结论是放弃原本的代码,用客户版的代码做基础,重新开发。
一开始的思路是用git做分支,但是由于客户版已经成型,重新整理代码提交版本比较费事,加上安卓端实际上就我一个人在做,频繁切换分支对我自己不是那么友好,最后没有采用。
后来IOS同事说,他们有一个叫target的机制,用来做衍生版本十分友好,遂觉得安卓绝壁有一个类似的东西。


productFlavors简介

先区分buildTypesproductFlavors。我自己的理解,前者叫做构建类型,是编译项目层面的概念,对应软件开发流程中的 编码-测试 两个步骤,后者叫做产品风格,是产品层面的概念,对应软件开发流程中的 发布 步骤。
buildTypes主要就是配置项目在构建时候的一些选项,比如签名信息,混淆,是否打印调试信息等,以及区分是发布版本还是测试版本。(可以参考我上一篇博客,这里不再详述)
productFlavors用来处理产品不同情景(渠道)下的不同表现。比如,同一个APP,捐赠版是更专业的功能代码,普通版是较为简易的功能代码。或者是最常见的是多渠道打包,可以更改不同渠道时候的不同图标,APP名,版本号,三方库等。

PS:如上理由,个人认为,区分测试版还是正式版应该用buildTypes节点,而不应该使用productFlavors

AS项目在新建之初,Module的Gradle没有productFlavors节点,此时打开Build Variants可以看到只有release与dubug这两个buildTypes选项,可以以图1的方式打开Module Settings(快捷键F12),切换到第三个选项卡Flavors选项卡来可视化的设置Flavors。
productFlavors节点下,若有配置信息,会在选择到该节点的时候,覆盖defaultConfig节点下的配置信息。
这里为了说明方便,我会新建一个空白项目,并且直接编写Gradle文件,不影响效果。

图1 Open Module Settings

为项目添加衍生版本

先明确几个概念:

  1. Android Studio中,applicationId跟packagename可以不同。在eclipse中两者为同一个值。
  2. Build Variants即构建变种版本,它的数量是buildTypes的数量 乘以 productFlavors的数量。
  3. SourceSets,被编译的源码以及资源文件合集。通过修改SourceSets中的属性,可以指定哪些源文件要被编译,哪些源文件要被排除。eclipse项目导入AS的时候会看到大量SourceSet节点。AS默认实现了两个SourceSet,main和test。

添加productFlavors节点

在Module的Gradle文件中,android 节点下添加一个produceFlavors节点,如下所示。


android {
    ...
    productFlavors {
        aa {
            // AppID
            applicationId 'com.zx.aa'
            // 版本号
            versionCode 1
            // 版本名称
            versionName "1.0.0"
        }
        bb {
            // AppID
            applicationId 'com.zx.bb'
            // 版本号
            versionCode 2
            // 版本名称
            versionName "2.0.0"
        }
    }
    ...
}

此时,我的构建变种版本会出现4个变种版本,如图2。你可以在编译器左下角找到它。

图2 构建变种版本数量.png

为每个衍生版本生成SourceSets

接下来要为每个衍生版本添加他们独有的代码以及资源文件。
首先,我们切换到Android目录(默认目录),右击选中一个Moudel,我的是空白项目,就选择名为app的Moudel。打开新建Activity的对话框,新建名为FirstActivity的Activity,Target Source Set选项选择aa,即现在给 名为aa的衍生项目 生成Source Set,并且新建一个名为FirstActivity的Activity。

另外要注意的一点是,由于在Android Studio中,applicationId跟packagename可以不同,此时,我选择了 名为aa的衍生版本,那么现在项目的applicationId就可以认为是com.zx.aa。但是在新建项目时,applicationId与包名都是com.zx.productflavorstest,所以此时虽然applicationId为com.zx.aa,但是为了保持一致,包名还是选择com.zx.productflavorstest。
见配图3。

图3 新建SourceSet.png

PS:其实这里只要保持 名为aa 的衍生项目与 名为bb 的衍生项目的包名一致就可以,如果这两个包名不一致会出现的问题下面会说。
PS:如果多次改动衍生版本的包名,在新建Activity的对话框中,package name可能会出现很多个曾经使用的包名。如果强迫症看的难受的话,可以在 项目root\.idea 中将其手动删除,具体在哪个位置,嗯,Ctrl+F搜索一下吧。

这样一来,名为aa的衍生项目的Source Set 就建立好了。
当然,这个步骤也可以手动完成,但是我们要秉承能偷懒就不动手的原则,况且手动建立麻烦不说还会出错,何不自动建立,一劳永逸。
建立好的SourceSet如图4所示,这里为了方便查看,我们切换到Project目录下。

图4 新建好的Source Set 目录.png

可以看到新建的SourceSet中,自动生成了java、res两个目录以及一个AndroidManifest清单文件,衍生版本除了代码,资源文件以及清单列表也可以各自定制,只不过在编译的时候他们会与main的SourceSet自动合并。
此时,我的项目里一共出现了4个SourceSet,分别为aa、main、androidTest、test,第一个为刚刚新建的,第二个为默认SourceSet,也是项目中所有衍生版本公用的代码以及资源部分。后两个为测试所用的SourceSet,在此不表。

同理,再为 bb项目 新建FirstActivity(与aa项目新建的Activity同名),生成它自己的SourceSet。结果与图4差不多,不再添加截图。
至此,为项目添加衍生版本的步骤就完成了,接下来看看它怎么用。

衍生版本的运用

从上文可以得知,每个衍生版本可以拥有自己的代码以及资源文件,他们处于不同的命名空间,所以可以重名。编译器通过选择不同的Build Variants来决定编译那一部分的代码,如之前的图2。

上文中我们在aa项目与bb项目的SourceSet中,各自新建了一个FirstActivity,现在我们打开新建项目时,自动生成的MainActivity,添加一个按钮,按钮的点击事件为跳转FirstActivity。



  btn_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(mContext, FirstActivity.class));
            }
        });

上文中,我们将aa项目与bb项目的包名均指定为com.zx.productflavorstest,与mian的包名相同,所以在编译器看来,他们共属于同一包下(由于SourceSet会在编译阶段合并)。
aa衍生版本与bb衍生版本包名相同,且他们在同一位置都有一个名为FirstActivity的类,所以从aa衍生版本切换至bb衍生版本,不会出现任何异常。若aa衍生版本与bb衍生版本包名不相同,在切换之后,会出现找不到FirstActivity的异常。

不同衍生版使用不同的桌面图标、图片、颜色值、字段同理,将不同的资源文件,分别放在每个衍生版本自己SourceSet的src文件夹中即可。

PS:不过还是很推荐自己将aa,bb衍生版本写为不同包名试一下,自己试一下,印象会比较深嘛。

发版

如果要发版上传软件市场,就把想生成的衍生版本选中生成就好,如果想一次生成所有衍生版本的安装包,可以

在android studio底栏中有个命令行工具Terminal,打开后就是CMD
可以自动切换到当前项目的目录下。
有的项目下会有graldew.bat
这个文件,你就可以输入这个命令:
gradlew assembleRelease就可以一次性生成所有的渠道包了

不过据说这个方法会出点小问题,所以我自己倾向于一个一个生成,反生每一个安装包都要重新确认一次,总体时间差不多。

其他细节

这里是一些不在讲述节奏里的知识点。

多渠道包

衍生版本最常见的一个应用就是多渠道打包。
简单的多渠道打包实际上不用SourceSet的参与,一般的多渠道是用友盟做的,我们知道友盟的多渠道代码为


        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="default" />

只需要在productFlavors设置几个节点



    productFlavors {
        aa {
            ...
            manifestPlaceholders = [UMENG_CHANNEL: "aa"]
        }

        bb {
            ...
            manifestPlaceholders = [UMENG_CHANNEL: "bb"]
        }
    }

然后改写友盟的代码为


        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL}" />

上一篇博客中,有将如何通过配置Gradle节点来更改APP的名称,原理是相同的。
这里也可以使用SourceSet,然后在生成的不同的清单文件里写入不同的渠道号,但是一般不推荐这么做,毕竟太麻烦了。

但是并不是说SourceSet在多渠道时候毫无用处,如果有这么一个需求:软件要在国内外应用市场上传,国内市场用极光推送,国外市场用谷歌服务。这种为了适配这种需求可能需要针对每个渠道订制推送代码,还需要分别导入不同的包,这就扯到另外一个东西,继续看。

不同的衍生版本导入不同的三方库

Gradle可以针对每一个buildTypes或productFlavors,导入不同的三方库,具体方法是 前缀 + Compile ,此时要符合驼峰,前缀为buildTypes或productFlavors中出现过的字段。比如,这个例子中,可以有aaCompile、debugCompile、releaseCompile。新建项目时候,经常看到的androidTestCompile,testCompile就是这个原理。


写在最后

这次博客的思路不是很清晰,因为所涉及概念比较多,有一部分本人还没有完全吃透,不过跟着博客敲一遍总是可以明白个七七八八,不至于项目卡在这里。
我个人觉得,productFlavors这个功能如果做 多渠道,或者是用来区分 完整版(捐赠版) 与 阉割版 的话,还是很方便的。但是如果这两个App的独有功能比较多,只是逻辑相似,界面相似,与它引起的麻烦比起来,他的优势基本感觉不到。
最让我感到烦躁的是aa项目与bb项目中同名Activity类无法改名,无时不在的版本上传问题,以及各种随时出现并且威胁到项目上线的小细节。无奈自己才疏学浅,Android端又只有我自己,不敢放开了折腾,这次只得作罢。所以以我们的需求,我还是在两版迭代之后,舍弃了这个方法,选择了依旧作为两个项目处理。

放一个参考链接吧,是我收获最大的一篇博客。Gradle for Android 第四篇( 构建变体 )
毕竟英语差,啃不动原版Android文档,以上所有收获均来源于网络博客,但无奈大部分博客都只有一些片段,本人以一个项目的思路进行了总结,共后来者参考。


个人理解,难免有错误纰漏,欢迎指正。转载请注明出处。

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

推荐阅读更多精彩内容