叨逼叨
公司产品线一直是有两个产品,大概是一个客户版,一个商户版。
两个产品一直是分开维护的,因为从外包接过来他就是这样,大家也都没觉得有啥不妥。
直到有那么一天,老板在周一晨会上说,“嗯,商户版咋那么丑,改了吧”。
o(゚Д゚)っ啥!o(>﹏<)o不要啊!
商户版跟客户版功能其实差不多,有很多公用的界面,加上少许的特有界面。由于一些历史遗留问题,最后得到的结论是放弃原本的代码,用客户版的代码做基础,重新开发。
一开始的思路是用git做分支,但是由于客户版已经成型,重新整理代码提交版本比较费事,加上安卓端实际上就我一个人在做,频繁切换分支对我自己不是那么友好,最后没有采用。
后来IOS同事说,他们有一个叫target的机制,用来做衍生版本十分友好,遂觉得安卓绝壁有一个类似的东西。
productFlavors简介
先区分buildTypes与productFlavors。我自己的理解,前者叫做构建类型,是编译项目层面的概念,对应软件开发流程中的 编码-测试 两个步骤,后者叫做产品风格,是产品层面的概念,对应软件开发流程中的 发布 步骤。
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文件,不影响效果。
为项目添加衍生版本
先明确几个概念:
- 在Android Studio中,applicationId跟packagename可以不同。在eclipse中两者为同一个值。
- Build Variants即构建变种版本,它的数量是buildTypes的数量 乘以 productFlavors的数量。
- 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。你可以在编译器左下角找到它。
为每个衍生版本生成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。
PS:其实这里只要保持 名为aa 的衍生项目与 名为bb 的衍生项目的包名一致就可以,如果这两个包名不一致会出现的问题下面会说。
PS:如果多次改动衍生版本的包名,在新建Activity的对话框中,package name可能会出现很多个曾经使用的包名。如果强迫症看的难受的话,可以在 项目root\.idea 中将其手动删除,具体在哪个位置,嗯,Ctrl+F搜索一下吧。
这样一来,名为aa的衍生项目的Source Set 就建立好了。
当然,这个步骤也可以手动完成,但是我们要秉承能偷懒就不动手的原则,况且手动建立麻烦不说还会出错,何不自动建立,一劳永逸。
建立好的SourceSet如图4所示,这里为了方便查看,我们切换到Project目录下。
可以看到新建的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文档,以上所有收获均来源于网络博客,但无奈大部分博客都只有一些片段,本人以一个项目的思路进行了总结,共后来者参考。
个人理解,难免有错误纰漏,欢迎指正。转载请注明出处。