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即可。

输出文件路径

可能遇到的问题

  • 由于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\""
        }
    }
}

修改读取配置文件代码

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

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的功能强大到我没法想象,好好学习,好好钻研。

技术,可以解放你。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容