Android的持续化集成及多版本打包

文档概述

关于Android开发,除了技术方面需要掌握,还有发布流程需要了解。本文档就包括以上两个方面,主要介绍:

  • 使用配置文件配置不同功能的apk
  • 使用gradle为Android构建签名包
  • Jenkins集成Android自动化打包
  • 使用gradle为Android生成不同配置的签名包

一、场景描述

在项目开发中,我们可能有多个功能有区别但是整体框架一致的工程。

情景:我们有一app,基础功能包括a,b,c,扩张功能包括d,e,f,其中客户1需要扩展功能d,f,客户2需要e,f。

二、配置文件简介

鉴于以上场景,开发app过程应该怎么做?如果客户1创建一份工程代码,客户2创建一份工程代码效率就太低了。因此我们需要另辟思路,采用配置文件的方式进行开发。

配置文件分类

关于配置文件目前有两种方式:

  1. apk文件写注释
  2. apk源码属性文件

配置方式的区别

方式1主要适用于轻量的注释,例如书写渠道名等一些简单的注释,不用修改源码。方式2就比较适合当前的场景,但是缺点在于配置文件在源码部分,所以修改配置文件就必须修改源码。

配置文件的使用

关于方式1的使用,可以: 美团批量打包

基本原理就是在apk文件生成之后,修改apk文件的部分字段,而不影响apk本身签名验证,在源码中根据apk安装的位置获取安装包文件再读取其中的字段。

方式2就是使用属性文件。实现方式就是使用java.util.Properties类进行文件加载。
预先在Android工程的main文件夹下创建assets文件夹,里面存放配置文件,客户1的配置文件命名为:a.propertiesb.properties,里面的内容如下:

fun_a=true
fun_b=true
fun_c=true
fun_d=true
fun_e=false
fun_f=true

读取配置属性代码:

public String funX(Context context, String x){
    Properties props = new Properties();
    try {
        props.load(context.getAssets().open("a.properties"));
    //  props.load(context.getAssets().open("b.properties"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return props.getProperties("fun_" + x);
}

在源码中根据对应函数的配置信息决定是否执行对应操作。

对于客户1和客户2的不同需求可以选择性的加载配置文件进行打包操作。

以上就是不同需求的具体操作。但是我们可以发现,这个操作效率太低,每次打包都要修改源码。下面介绍自动化过程。

三、 自动化之gradle打包

配置操作

为了之后可以执行自动化操作,签名必须也要能进行自动化执行。

首先放置签名文件到:项目的根目录。

modulebuild.gradle文件中添加以下内容:

apply plugin: 'com.android.application'

def keystorePSW = ''
def keystoreAlias = ''
def keystoreAliasPSW = ''
// default keystore file, PLZ config file path in local.properties
Properties properties = new Properties()
// local.properties file in the root director
properties.load(project.rootProject.file('gradle.properties').newDataInputStream())

keystorePSW = properties.getProperty("keystore.password")
keystoreAlias = properties.getProperty("keystore.alias")
keystoreAliasPSW = properties.getProperty("keystore.alias_password")

android {
    ...
    signingConfigs {
        release {
            keyAlias keystoreAlias
            keyPassword keystoreAliasPSW
            storePassword keystorePSW
            storeFile file('../xxx.jks')            // 签名文件的位置
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}
dependencies {
    ...
}

为了能够在Jenkins环境也能使用自动打包操作,在项目的根目录gradle.properties文件中添加如下内容:

keystore.password = xxx             // 密钥的密码
keystore.alias = xxx                // 密钥的别称
keystore.alias_password = xxx       // 别称的密码

随后在项目的根目录下使用gradle即可进行打包。

执行命令

打包命令:

gradlew clean               // 清除build文件夹
// 二选一
gradlew build               // 检查依赖并编译打包,会生成debug和release两个包
gradlew assesmRelease       // 生成release包

执行以上命令之后,会在项目的app/build/output/apk/*.apk生成对应的apk。

注:Windows操作系统使用gradlew,Linux系统使用./gradlew

以上,使用gradle自动打包就以完成。下面就是集成Jenkins持续集成环境了。

四、Jenkins集成

创建项目

关于构建介绍可以参考:Jenkins+Gradle实现android开发持续集成、打包

配置信息没什么特别的。主要是输出文件路径:直接填写app/build/output/app/*.apk即可。

Android的持续化集成及多版本打包_第1张图片
输出文件路径

可能遇到的问题

  • 由于Jenkins自动构建,所以对语法要求比较严格。如果出现以下错误:

      FAILURE: Build failed with an exception.
      * What went wrong:
      Execution failed for task ':app:lint'.
      > Lint found errors in the project; aborting build.
       
        Fix the issues identified by lint, or add the following to your build script to proceed with errors:
        ...
        android {
            lintOptions {
                abortOnError false
            }
        }
        ...
      
      * Try:
      Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    

    这个是因为代码不符合规范,lint检查时报错,因此中断了整个编译过程。

    只要在当前app的app/build.gradle文件内增加如下代码:

      android{
          ...
          lintOptions{
              abortOnError false
          }
          ...
      }
    
  • 安装Jenkins配置局域网访问

    在mac安装Jenkins之后,局域网无法访问,原因在于使用brew安装jenkins会避免很多其他安装方式产生的用户权限问题,但是会将httpListenAddress默认设置为127.0.0.1,这样我们虽然可以在本地用localhost:8080访问,但是本机和局域网均无法用ip访问。解决办法为修改两个路径下的plist配置。

      ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
      /usr/local/opt/jenkins/homebrew.mxcl.jenkins.plist
    

    修改之后重启Jenkins即可访问。

      brew services start jenkins         // 开启服务
      brew services stop jenkins          // 关闭服务
      brew services restart jenkins       // 重启服务
    
  • 更多错误查看:

    使用Jenkins持续集成Android项目遇到的坑

通过以上操作,Android项目就可以使用Jenkins自动集成了。

但是我们第一部分的场景问题还是没有解决,如何自动化区分客户的版本呢?

五、自动化版本区分

Android官网介绍了 构建变体

通过配置不同的 productFlavors我们可以获取不同版本的apk。

因此第一部分的需求通过以下操作实现。

更新buidl.gradle文件

android {
    ...
    buildTypes {
        ...
    }

    productFlavors {
        fun_a {
            buildConfigField "String", "CONF_NAME", "\"a.properties\""
        }
        fun_b {
            buildConfigField "String", "CONF_NAME", "\"b.properties\""
        }
    }
}
  • 注1:字段解释参考 GRADLE自定义你的BUILDCONFIG
  • 注2:使用String属性时候,值需要使用\"进行转义

修改读取配置文件代码

配置属性文件不变,读取的代码进行如下修改:

public String funX(Context contex, tString x){
    Properties props = new Properties();
    try {
        props.load(context.getAssets().open(BuildConfig.CONF_NAME));
    } catch (IOException e) {   
        e.printStackTrace();
    }
    return props.getProperties("fun_" + x);
}

之后使用打包命令就会生成两个apk安装包,一个是客户1定制的功能,一个是客户2定制的功能。

输出路径不变,生成包名:

  • app-fun_a-releas.apk
  • app-fun_b-releas.apk

这样,Jenkins一次编译也就可以获取到正确的版本。

修改输出文件的包名

以上操作之后已经可以正确获取目标apk,但是并不直观。如果可以在输出文件名中添加输出版本号和打包时间就完美了。

在项目的build.gradle文件中,最后节点添加:

def releaseTime() {
    return new Date().format("yyyyMMdd-HHmm", TimeZone.getTimeZone("GMT+08:00"))
}
android{
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (variant.buildType.name.equals('release')) {
                def fileName = outputFile.name.replace("app-","").replace("release", "v${defaultConfig.versionName}-${releaseTime()}")
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }
}

修改之后输出文件名:

fun_a-v2.0.5-20171115-1655.apk
fun_b-v2.0.5-20171115-1655.apk

以上。

六、小结

通过使用gradle+Jenkins,可以让程序员从繁复的打包任务中解放出来,更多时间去做核心开发的相关业务。

gradle的功能强大到我没法想象,好好学习,好好钻研。

技术,可以解放你。

你可能感兴趣的:(Android的持续化集成及多版本打包)