阅读 461

Android Gradle最佳实践系列4:创建Build Variants

当我们在开发一款应用时,通常会面临发布不同的版本需求。举两个常见的场景,场景一:我们正在开发新功能,开发完成后需要发布提测版本提交给QA测试人员,测试通过后再发布线上版本,这时线下版本和测试版本的服务器接口域名不一样又或者有不同的api接口;场景二:我们的app需要发布一个免费版本和付费版本,付费版本会有更高的使用权限。针对如上两种情况我们就需要发布四个apk,免费QA版,免费线上版,付费QA版,付费线上版,如果在代码里面硬编码会使得项目异常复杂。gradle针对这种情况,提出了解决这种问题的方法,对于QA版或线上版可以配置build type,Androidstudio默认配置了Debug和Release两种type,对于付费版或者免费版,可以通过配置Build flavors。这两种类型组合起来就叫做build variant

Build types

Gradle在Android中的build type是用来处理app或者library应该被构建成什么类型,在这个配置中,我们可以定义应用的包名是什么,是否自动去除掉没有引用的资源,是否开启混淆等等。具体配置可参考如下代码

android {
       buildTypes {
           release {
               minifyEnabled false
               proguardFiles getDefaultProguardFile
                 ('proguard-android.txt'), 'proguard-rules.pro'
           }
      }
}
复制代码

当我们新建一个项目或者模块时,build.gradle文件会默认配置上release的build type,默认配置关闭了混淆功能(即minifyEnabled设置为false)和定义混淆文件的位置。 除了release build type再就是debug build type也是默认创建好的,但是没有在代码里显示出来,如果你想修改默认Debug中的配置,需要单独声明出来。

debug的build type默认设置debug属性为true,方便调试

创建build types

当我们觉得默认的两种类型不够用时,我们能很容易的自定义build type,如下代码展示了创建一个新的build type名叫qa_test的类型

android {
    buildTypes {
        qa_test {
             applicationIdSuffix ".qatest"
            versionNameSuffix "-qatest"
            buildConfigField "String", "API_URL",""http://staging.example.com/api""
          }
    }
}
复制代码

上面的代码主要配置qa_test的类型功能为:给包名加上了.qatest的后缀,这样我们能在手机上安装相同的程序,因为应用包名不一样;在version name上加上了-qatest的后缀,然后再BuildConfig类中加了一个API_URL的string属性。

通过buildConfigField配置,会在编译后BuildConfig类中增加对应定义的常量

在自定义build type是,还可以重用已用的build type,如下代码所示

 android {
       buildTypes {
           qa_test.initWith(buildTypes.debug)
           qa_test {
               applicationIdSuffix ".staging"
               versionNameSuffix "-staging"
               debuggable = false
            } 
        }
}
复制代码

**initWith()**方法为qa_test类型拷贝了debug类型的所有属性,但是我们也可以修改其中的属性,通过显示声明出来。

build types代码目录结构设置

当我们创建一个新的build type,gradle也会默认指定一个同名的目录在项目里面,但是不会把目录自动创建出来,所以需要我们手动创建一个同名的目录,其结构如下所示

这种配置可以使得我们在任意build类型去自定义修改代码,比方说在release的类型登录界面带上正式版本的Logo,在debug版本的登录界面就带上测试版本的Logo

Tips:当我们创建了不同的build type,并在不同的type文件目录里面做不同的修改,但使用的源是基于main目录下的代码,比方说我们要在不同的type里面有不同的login界面,那么main包下面就不能包括LoginActivity,而只在不同buildtype下加入各自自定义的LoginActivity,如果我们也在main目录下加入,编译器会提示dumpllicated file的错误

对于Resources资源,和处理Java代码有些许不同,对于layout、Drawable资源和图片,如果在不同的type里面定义了,那个定义的这些文件会直接替换掉main目录里面相同的资源文件;而对于String,Color的资源则会采取合并的策略,举个例子,如我们在main下的string.xml如下

 <resources>
       <string name="app_name">TypesAndFlavors</string>
       <string name="hello_world">Hello world!</string>
</resources>
复制代码

如果qa_test type中的string.xml如下

 <resources>
       <string name="app_name">TypesAndFlavors QA_Test</string>
</resources>
复制代码

合并后的string.xml如下所示

 <resources>
       <string name="app_name">TypesAndFlavors QA_Test</string>
       <string name="hello_world">Hello world!</string>
</resources>
复制代码

如果我们没有自定义string资源文件,那gradle就会默认使用main目录下的string。对于AndroidManifest.xml文件也是一样的合并策略,如果要修改manifest文件,只需要在对应的build type目录中的manifest文件加入我们需要的代码,gradle会自动合并,在后续部分会更深入的介绍gradle合并的原理

依赖

每一个build type有可能有自己独立的依赖,如果我们配置了,gradle会自动识别配置,例如要在debug type中加入一个日志框架,可以参考如下代码

 dependencies {
       compile fileTree(dir: 'libs', include: ['*.jar'])
       compile 'com.android.support:appcompat-v7:22.2.0'
       debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}
复制代码

debugCompile表示只在debug type中把log4j加入到编译路径中,关于常用的集中依赖scope请参考系列三:Gradle依赖管理

Product flavors

与build type相反,build type用来构建不同类型的app,debug版或release版,而produc flavors则是用来在同一个app上创建不同的版本,如付费版和免费版。一个非常常见的应用场景就是我们创建了一个银行管理的app给不同银行提供服务,但是不同的银行App的logo不一样,有produc flavors就能在基于一套代码上创建不同版本的app或者library。

如果我们不确定什么时候用build type,什么时候该用product flavors,那么问自己几个问题,如果是构建不同类型供内部使用或者是在google palay上提交一个新的app,建议使用build type;如果app要分发在不同的渠道上,那么建议product flavors

创建 product flavors

创建product flavors与build types非常相似,你只需要添加productFlavor代码块,如下代码所示

 android {
       productFlavors {
           red {
               applicationId 'com.gradleforandroid.red'
               versionCode 3
            }
         blue {
               applicationId 'com.gradleforandroid.blue'
               minSdkVersion 14
               versionCode 4
            }
      }
}
复制代码

Product flavors与build types属性不同,因为product flavors其实是ProductFlavors类,build.gradle文件中默认添加的defaultConfig也是ProductFlavors类的实例。

product flavors代码目录结构设置

与build type的设置类似,product flavors也可以配置自己的代码目录,可参考Build Type目录结构设置,这里不再赘述。

Multiflavor variants

在某些情况下,我们可能需要进行flavors的组合,比方说你的app有两套主题,绿色主题和红色主题,然后有两个版本,付费版和免费版,你可能需要进行组合,类似于红色免费版,红色付费版,绿色免费版,绿色付费版。通过使用flavorDimensions可以解决flavors组合的问题,配置可参考如下代码

android {
       flavorDimensions "color", "price"
       productFlavors {
           red {
               flavorDimension "color"
           }
           blue {
               flavorDimension "color"
          }
         free {
               flavorDimension "price"
           }
            paid {
               flavorDimension "price"
         }
      }
}
复制代码

当我们添加了flavor dimension之后,gradle会判断我们是否为flavor指定了flavorDimensions中设定的值,如果没有设置,gradle编译的时候就会报错,而且flavorDimensions中设置的顺序也是非常重要的,比方说如果红色主题的flavor和付费版的flavor中某处代码修改是一样的,那么在运行的时候就以flavorDimensions中配置的先后顺序为准,根据上面的配置,就是以红色主题中的代码为准。

假设我们的build type的类型是配置的debug和release那么根据上面代码的配置生成的build variants为如下

  • blueFreeDebugblueFreeRelease
  • bluePaidDebugbluePaidRelease
  • redFreeDebugredFreeRelease
  • redPaidDebugredPaidRelease

Build variants

build variants就是build type和product flavors组合的结果,当我们创建一个新的build type或者product flavor的时候,新的variants也就相应的创建了。例如,如果我们的build type是标准的debug和release,那么创建一个product flavor为red theme和blue theme的时候,相对应的build variants为自动生成。

如上截图为Android Studio中Build Variants的工具框,默认在Android Studio界面的左下角边缘倒数第二个,亦或可以通过View | Tool Windows | Build Variants打开。

如果我们没有配置product flavors,variants只会包括build types。如过我们也没有配置过build type,Gradle的Android插件也会默认配置debug build type,所以build variants不会出现为空的情况。

Tasks

Gradle的Android插件会根据我们配置的build variant自动生成task。一个新的Android app项目会默认有debug和release两种build type,所以我们可以运行assembleDebugassembleRelease去生成不同apk,或者直接运行assemble来生成两种build type的apk。当我们新添加build type,就会相应的添加新的task。当添加新的flavors product,task重新生成,变化会比较大,因为每一个product flavor和build type是组合的。所以即便是最简单的一个build type和一个flavor的组合,都会生成有三个对应的task:

assembleBlue 使用Blue flavor,相当于运行assembleBlueReleaseassembleBlueDebug

代码目录配置(Source sets)

Build variants就是build type和product flavor的组合,比如我们源代码中有main,releas,debug,red四个目录,其中,release和debug是build type的类别,red是设置的productflavor,那个对应的build variant就是redReleas,redDebug,也就是说redReleas使用的源码是red和release目录的合并,同理redDebug使用的是red和debug目录中代码的合并。

资源文件和manifest文件的合并

不同的build type和product flavor有不同的源码目录,在生成不同的build variant包时,会需要合并资源,例如我们在debug build type中的manifest文件设置了要存储log日志的权限,但是在main目录中的manifest文件却不需要,这样在生成编译debug build variant的时候就需要合并main目录中的manifest文件和debug目录中的manifest文件。其中选取的资源的优先级如下图所示:

如上可知,如果flavor中有一个图片资源为logo.png,main目录中也有一个图片资源为logo.png。在打包的时候,因为flavor的优先级大于Main所以,会使用flavor中的logo.png

关于资源和manifest文件的合并,有很多具体的细节在这里没法阐述,官方文档给了更多更详细的解释,可以访问这个地址 [Android Developer:Manifest-Merger](tools.android.com/tech-docs/n… system/user-guide/manifest-merger)

创建build variants

gradle使得处理复杂的build varinats非常容易,即使我们创建了两种build type和两种product flavors。build文件中代码还是非常清晰明了

android {
       buildTypes {
           debug {
               buildConfigField "String", "API_URL",
               ""http://test.example.com/api""
         }
           staging.initWith(android.buildTypes.debug)
           staging {
               buildConfigField "String", "API_URL",
                 ""http://staging.example.com/api""
               applicationIdSuffix ".staging"
           }
      }
       productFlavors {
           red {
               applicationId "com.gradleforandroid.red"
               resValue "color", "flavor_color", "#ff0000"
           }
           blue {
               applicationId "com.gradleforandroid.blue"
               resValue "color", "flavor_color", "#0000ff"
         }
   }
}
复制代码

在上面的代码中,我们创建了四种build variants:blueDebug,blueStaging,redDebug,redStaging。每一个variant都自定义了API URL 和flavor color。运行程序blueDebug可能为如下所示

而redStaging可能如下图所示

第一个截图是blueDebug,使用的是debug build type中的URL和blue product flavor中的颜色,同理可类推redStaging

Variant filters

在某些情况下,可能我们不想使用某种build variant,例如现在有debug和release的build type,red和blue的flavors,但是,blue还是测试环境根本现在用不到blue的release版本,那么我们可以直接过滤掉,在android studio中的buildVariants窗口就不会出现了,过滤可以在app模块或者library模块的build.gradle文件中加入如下代码:

android.variantFilter { variant ->
    if (variant.buildType.name.equals('release')) {
        variant.getFlavors().each() { flavor ->
            if (flavor.name.equals('blue')) {
                variant.setIgnore(true);
            }
        }
    }
}          
复制代码

加入以上代码后,再同步项目,在AndroidStudio的BuildVariants窗口就可以看见没有了blue release的版本了

Signing Configuration(apk签名配置)

在将app发布到Google Play或者其他应用市场之前,我们需要对apk进行秘钥签名。如果我们针对用户有免费和付费两个版本,那么需要给每一个flavor不同的签名,签名的配置代码参考如下

 android {
       signingConfigs {
           staging.initWith(signingConfigs.debug)
           release {
               storeFile file("release.keystore")
               storePassword "secretpassword"
               keyAlias "gradleforandroid"
               keyPassword "secretpassword"
            }
        }
}
复制代码

在上面的例子里,我们创建了两种不同签名配置

debug类型的签名配置是由gradle的android插件自动生成的,密码的alias name都是默认的。

Tips:密码建议不要写在build.gradle文件中,可以写在local.properties文件中

加入了签名配置后,就可以使用签名信息了,代码可参考如下

在buildType中使用

 android {
       buildTypes {
           release {
               signingConfig signingConfigs.release
            }
        }
}
复制代码

在flavors中使用

 android {
       buildTypes {
           release {
               productFlavors.red.signingConfig signingConfigs.red
               productFlavors.blue.signingConfig signingConfigs.blue
            } 
        }
}
复制代码

由上代码可知,我们不能如下配置

 android {
       productFlavors {
           blue {
               signingConfig signingConfigs.release
            } 
        }
}
复制代码

因为在合并build type和flavor时,flavor会覆盖type中的签名

总结

在这一部分,主要讨论了build type,produc flavors,和两者的组合以及组合后源码,资源文件的一些细节处理;再就是介绍了签名的配置。

文章分类
Android
文章标签