App环境分离的实现:Android篇

我在App架构经验总结中有简单提到环境分离的实现方案,但没有深入讲实现细节。本系列则打算用两篇文章分别详细讲讲Android和iOS环境分离的具体实现,本篇则先讲Android的实现。

概念解析

本文的实现方案是基于Gradle的,因此,有几个概念需要先了解清楚。

applicationId

没有Gradle之前,我们都知道,包名是Android程序的唯一标识,通过在AndroidManifest.xml文件中定义package属性。同时,这个包名也是引用资源的路径名,即R资源文件的包名。

引入Gradle之后,就多了一个applicationId的概念。官方解释是:applicationId是程序的唯一标识,而package则用来引用R类以及解析相关的activity/service注册操作。因此,可以设置多个不同的applicationId,对应多个不同版本的apk,而package则保持一致。新建的项目默认applicationId和package是一样的。

Product Flavors

通过Product Flavors可以创建不同的产品渠道版本,网上流行的多渠道打包方案大部分都是通过添加多个渠道的Flavors来实现的。Flavors一般设置的属性如下图,当然,实际上不只是这些,例如manifestPlaceholders就不在此设置界面里。默认有defaultConfig这个Flavors。

Build Types

Build Types则是构建类型。默认提供debug和release两种类型,如下图所示。主要提供是否可调试、是否混淆等构建打包时的相关配置。

Build Variants

Product Flavors和Build Types的组合形成了多个Build Variant,例如,如果有两个Product Flavors版本:

productFlavors {
    free { ... }
    enterprise { ... }
}

而Build Types也有两种类型:

buildTypes {
    debug { ... }
    release { ... }
}

那么,组合出来的Build Variant就有四个版本了:

  • freeDebug
  • freeRelease
  • enterpriseDebug
  • enterpriseRelease

环境分离实现

相关概念都了解清楚了,那么,环境分离具体如何实现呢?其实,实现方案不止一个,这里,我提供两个方案,一个可以使用Product Flavors实现,一个可以使用Build Types来实现。

方案一

Product Flavors用来定义产品渠道,假如我们有两个环境:测试环境和生产环境。那么,定义两个Flavors分别对应测试环境和生产环境,并且applicationId不同,可以如下定义:

productFlavors {
    beta {
        applicationId 'com.domain.productname.beta'
    }
    production {
        applicationId 'com.domain.productname'
    }
}

这样,测试环境和生产环境实际上等于是已经分开的两个app了,已经可以在同一个设备里同时安装测试版本和生产版本了。但这样是不够的,两个app将会一模一样,很难区分。因此,我们也要修改两个环境版本的应用名称,同时还要使用不同的应用图标。怎么做呢?

在app模块,src目录下,新建一个与main目录同级的beta目录,beta目录的结构如下图:

新建的beta目录对应于productFlavors的beta版本,beta版本目录下的图片资源ic_launcher.png对应于beta版本的应用图标,这要与main目录的ic_launcher.png保持一致的位置和名称,只是图片不同。而beta版本的strings.xml则只需要包含一个属性,如下:

<resources>    <string name="app_name">appNameBeta</string></resources>

至此,当运行beta版的Build Variant时,则会看到beta版的应用图标和应用名称也与生产版的不同了。

这里要说明一下原理。首先,productFlavors定义的每个渠道是会继承自defaultConfig的,就是说上面定义的beta和production都继承了defaultConfig。然后,productFlavors每个渠道里自定义的属性会覆盖defaultConfig相应的属性,beta和production定义了applicationId,则会覆盖了defaultConfig的applicationId。这里,因为production自定义的applicationId和defaultConfig的applicationId是一样的,所以其实也可以取消applicationId的自定义。

而对于源代码部分,当运行beta版的Build Variant时,默认会引用main目录的资源文件,而beta目录下的资源会覆盖main目录相应位置的资源,例如上面beta目录下的mipmap目录的ic_launcher.png就会覆盖main目录下相应位置的ic_launcher.png;同样的,beta下的strings.xml里定义的app_name就会覆盖main目录下的strings.xml定义的app_name。

但对于java类则不同,beta里定义的java类并不会覆盖main目录里相应的java类。事实上,两个目录的java类只能有一份,否则会出现类重复的错误。如果beta和production版本需要有同个页面不同的实现,例如有一个Activity需要不同的实现,那么,只能新建一个production版本目录,然后该Activity类在beta和production都有一份拷贝,代码实现可以不同,但main目录下则不能有该Activity类。

方案二

前面就已经说过,Build Types默认提供了debug和release两种类型,其实也可以分别对应于测试和生产环境。在Build Types的设置界面中,可以看到有个Application Id Suffix的设置选择,这个可以用来设置applicationId的后缀。这个后缀是相对于Flavors来说的,比如我们定义了一个如下的productFlavors:

productFlavors {
    free {
        applicationId 'com.domain.productname.free'
    }
    enterprise {
        applicationId 'com.domain.productname'
    }
}

而debug类型的Application Id Suffix设置为“.debug”,那么对应的freeDebug版本的applicationId就是’com.domain.productname.free.debug’,而enterpriseDebug版本的applicationId则是’com.domain.productname.debug’。

对于本方案二来说,并不需要再定义额外的productFlavors,使用默认的即可。那么,debug类型的Application Id Suffix可设置为”.debug”,release类型则无需设置。

接着,src目录下新建一个debug目录,和方案一的beta目录完全一样,只是目录名称不同。另外,如果两个版本需要有同个页面不同的实现,那么,也和方案一一样,需要新建个release目录,和production目录一样。

写在最后

如果只是从环境分离来说的话,我更倾向于方案二,因为系统默认就提供了debug和release两个版本,而对应的Build Variant也只有两个。如果采用方案一,那么将产生四个Build Variant,这显得有点多余。


原文发布于微信公众号 - Keegan小钢(keeganlee_me)

原文发表时间:2016-03-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOSer成长记录

iOS-多线程死锁问题

虽然作者在开发中经常会用到多线程,但是对于死锁、线程阻塞等问题还是比较模糊,一般都是先写吧,有问题再改,现在闲下来了,是时候总结一波了,本文主要总结一下同步/异...

841
来自专栏抠抠空间

Django之logging日志

简介 Django使用python自带的logging 作为日志打印工具。简单介绍下logging。 logging 是线程安全的,其主要由4部分组成: Log...

8386
来自专栏闵开慧

hibenate中log4j.properties文件添加到eclipse中

Hibernate用log4j包来做日志输出,这就要求项目中创建一个log4j的配置文件log4j.properties,否则有些运行日志就无法看到(不会影响程...

3505
来自专栏hotqin888的专栏

EngineerCMS用beego实现swagger API自动化文档

2423
来自专栏吴伟祥

Webjar是什么

4692
来自专栏阮一峰的网络日志

RESTful API 最佳实践

它的大原则容易把握,但是细节不容易做对。本文总结 RESTful 的设计细节,介绍如何设计出易于理解和使用的 API。

1614
来自专栏blackpiglet

配置 SSH 端口转发,并设置开机启动

为了降低运维成本,提高可靠性,物理服务器往往都不再部署在本地,IDC 托管成了更多企业的选择。服务器托管在 IDC 后,出于安全的考虑,不会直接开放所有服务器的...

2023
来自专栏技术支持log

SELinux导致的导致的服务端口监听失败

SELinux (Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统。...

7111
来自专栏康怀帅的专栏

Docker Registry v2 配置文件详解

/etc/docker/registry/config.yml 详解。 你可以在 docker run 时通过 -e 参数设置环境变量来配置。为了避免命令的繁杂...

4205
来自专栏Linyb极客之路

RESTful API 最佳实践

它的大原则容易把握,但是细节不容易做对。本文总结 RESTful 的设计细节,介绍如何设计出易于理解和使用的 API。

3154

扫码关注云+社区

领取腾讯云代金券