build variants on android studio

Foreword

Android studio作为google官方推荐的Android开发IDE,的确是一个很棒的开发工具,虽然它曾经那么难用不堪,不过成长下来,已然能够完全取代eclipse成为Android程序员们最应该使用的开发工具。Gradle工程对于打包环境配置能够让你从冗余的xml配置中解放出来,在这里我讲介绍一下Android Studio的gradle中的build variant相关的一些小知识。

Android的build variant主要由build types和product flavors做笛卡尔乘积得到的集合(主要的意思是还会有其他一些比如针对cpu架构的,在此不做详细介绍).

Build types

Android Studio默认有两个build type类型,releasedebug,以下是他们之间的一些区别:

  • 1: 在debug类型中,debuggable被默认置为true, 而在release中默认是false, 该值在生成项目(build project)时,会和BuildConfig.DEBUG字段对应,通过它我们可以做一些逻辑处理。
  • 2: debug类型默认使用debug签名,而在release中,你必须手动指定一个签名。更多关于签名的信息,请点击这里

我们还可以添加自定义的build type,需要注意的是其名字不能和渠道名(product flavor)相同。在自定义的build type中,我们需要根据自己的需求设置对应的属性。例如

manifestPlaceholders = [INSTALLED_CHANNEL: "default_channel"]
debuggable true
versionNameSuffix  ".alpha"
// 开启混淆
minifyEnabled true
zipAlignEnabled true
// 移除无用的资源文件
shrinkResources true
proguardFiles 'proguard-rules.pro'
// 签名设置
signingConfig signingConfigs.release

等等,以及其他一些属性。

在第一点我们提到可以通过BuildConfig.DEBUG来在java代码中写一些逻辑,比如我们要在开发过程中输出程序调试日志,而在正式发布的包里面把这些日志去掉。我们可以这么做:

if(BuildConfig.DEBUG) {
	Log.d("Test", "hello, this is a debug log");
}

因为BuildConfig.DEBUG为Android Studio编译项目时生成的自动生成的java类,查看其声明我们发现其是一个static final字段。编译器在编译时,会帮助我们把if语句中条件永远为false的语句去掉,不编译为二进制文件,因此在最终的发布包中,我们就看不到这些调试日志了。

这是一个最简单的使用BuildConfig.DEBUG的示例,关于更深入的使用BuildConfig.DEBUG来编写程序逻辑,在下面介绍完渠道(Product Flavors)后详细介绍。0

Product Flavors

通过Product Flavors可以配置多渠道信息,针对不同的渠道,我们可以给程序添加不同的版本标识以及逻辑代码。

添加渠道很简单,只要在对应的module下面的build.gradleandroid块中添加productFlavors代码块即可,如下所示:

android {
	
	...

	productFlavors {
		free{}
		pro{}
		...
	}

	...
}

如上所示,一共添加了三个渠道,free, pro, 添加完后,点击菜单栏的Build->Make Project(或者Rebuild Project)重新生成项目即可。如果在此之前,我们的项目是一个正确的Gradle工程,在修改了build.gradle文件之后,Android Studio会提醒我们同步项目(Sync Project), 点击Sync Now,则相当于重新生成了项目。

在项目构建完成之后,我们可以点击Android Studio左侧的Build Variants标签页,我们可以看到我们项目中的Module信息,每个Module后面都显示了其当前选中的Build Variant,我们在直接运行代码时,编译的正是该variant.

在这里我们也可以看到如上述所说的,每个Build Variant都是Build TypesProduct Flavors的笛卡尔乘积的其中一个元素。

更复杂的逻辑配置

我们可以给每个不同的Product Flavors指定不同的包名、版本号和版本名, Build Type可以指定不同的或者版本名后缀名,这些都只是简单的表层配置,更复杂的配置则是为他们分配不同的代码,上面提到的BuildConfig.DEBUG便是其中一种。

一般情况下,我们设置根据不同场景调用不同的逻辑时,会在代码中根据动态的结果来调用不同的逻辑,即if语句中的代码是动态的,与BuildConfig.DEBUG不同的是,BuildConfig.DEBUG(以及所有if等语句中逻辑永远为false)判断的代码在编译时会直接移除。除此之外,如果要根据不同的渠道实现不同的逻辑,我们还可以在src目录下创建与渠道名同名的文件夹,该文件夹与src/main处于同级目录,src/main的代码为程序主要代码,该文件夹下主要由java, res, assets等文件夹以及AndroidManifest.xml文件,区别是渠道文件夹下的这些文件,可以覆盖或补充src/main文件夹下的代码,组成一套完整的代码。

这部分代码比较复杂,在此不详细介绍了,可以点击文章最后的demo链接下载代码查看。

更多的小技巧

  • 1:除了debuggale生成的DEBUG变量之外,BuildConfig类中还会有VERSION_CODE, VERSION_NAME, APPLICATION_ID等一系列常量,除此之外,我们还可以自定义一些变量来区分各个渠道,只需要在具体的渠道或者编译类型子块中加入如下代码:
buildConfigField "boolean", "IS_FREE", "true"

其中第一个字段是java数据基础类型,第二个是字段名,第三个是值。当值与基础类型不对应时,生成的BuildConfig会有错误,便无法生成项目。

我们可以配置多个类似的预编译值(如果确实有需要),然后在src/main中使用。

  • 2:当我们有多个渠道,而其中两个或者多个渠道要使用不同的java代码,其余几个渠道代码与某个渠道代码相同时,我们无法在src/main中放置这些Java类,因为会有类重复冲突(duplicated error),如果在每个渠道的文件夹下都放置同样的java代码,难以维护,而且会造成困惑。 我们可以通过如下方法来达到我们的目的:

android {

	productFlavors {
        flavorA {}
        flavorB {}
        flavorC {}
        flavorD {}
		...
    }

	productFlavors.all { flavor ->
        if (name.equals("flavorA")) {
            // do something for flavorA
            // Note: we do not set the sourceSets for flavorA here, you should put the source code in `src/flavorA/java`, gradle would do the work for us.
        } else {
            // set the sourceSets for other flavors
            initSourceSets(flavor.name)
        }
    }
    
}
    

def initSourceSets(flavorName) {
    android.sourceSets.findAll { source ->
        source.name.equals(flavorName)
    }.each { source ->
    	// set the sourceSets as flavorB
        source.setRoot('src/flavorB')
    }
}

最后的最后

关于gradle配置还有很多具体的细节,我也在不断的探索中,常总结常思考,才能常进步,加油~~~

附上很简单的Demo地址: FlavorDemo

blog comments powered by Disqus