Jenkins-ci容器化在Android项目构建中的应用

阿凡达2018-08-10 10:01

随着软件开发复杂度的不断提高,如何能在不断变化的需求中快速适应和保证软件的质量显得尤其的重要,持续集成正是针对这一类问题的一种软件开发实践。文中的Jenkins-ci容器化方案能够在项目构建过程中提取出更多自定义的需求信息并进一步将持续集成的环境参数配置标准化,通过Docker的形式实现快速部署。

遇到的一些问题

在产品的实际开发中,可能会遇到一些问题。例如要做一个app产品,大致流程如图所示:

图中的代码检查和测试这部分,过程重复固定,比较费时费力,如果能够通过自动化构减少大部分这类工作那是最好不过了。

还有一些用户可能希望在产品迭代过程中,能够实时了解产品迭代的相关信息。例如某天新增了什么功能、修复了jira上的哪些问题,某一次代码的提交对整个Apk产生的影响,如方法数和大小等等。

解决了一些问题

针对上述的一些问题,我们借助于持续集成框架Jenkins,能够很好的解决这些问题,并且进一步将Jenkins配置环境容器化,实现快速配置。

也许有人会问,公司内部已经有Jenkins持续集成这类技术支持了,为啥还要做呢,那肯定是我们在做的东西他们还没有呀,比如我们对自动化构建的内容进行了各种定制(包括Android LintFindBugsFireLineApk包大小和方法数统计、资源文件统计,以及Git commit日志分析),并提供可视化的构建报告等。

我们通过在Jenkins中加入一些自定义的构建脚本、任务等,使得项目在自动构建过程中能够收集、整理、统计相关数据,并以报表的形式呈现给用户。配置完成的Jenkins运行之后主界面如图,只需要进行简单的配置即可,不用修改项目,也不会对项目产生影响。

并且,Jenkins-ci容器化方案支持两类构建通知:

  • 提交代码编译不通过时及时通知到用户
  • 每天生成一份详细的工程构建报告

编译报错通知和每日的工程构建报告都会通过邮件的形式发送,其中,构建报告的内容主要包括基础构建信息、代码分析、资源文件分析、打包产物Apk分析、Git日志分析和构建日志信息六个部分,如图:

Jenkins-ci容器化

Jenkins-ci容器化方案主要包含自动化构建内容定制和结合Docker实现快速配置两个部分内容。

自动化构建内容定制

本次自动化构建将包括Android LintFindBugsFireLineApk包大小和方法数统计、资源文件统计,以及Git commit日志分析几个部分。下面,将对这些自动构建任务进行一一讲解。

这里需要提到的一点就是,上述的这些构建任务会在Jekins环境中通过脚本的形式动态添加并执行,不需要用户干预,避免修改项目代码。

静态代码质量检查

1. Android Lint

Android Lint是一个静态代码分析工具,也是Android Studio默认的代码检查工具。

默认Android Lint规则

它默认的检查规则有很多,常见的有6大类:

  • 正确性(Correctness)
    • ScrollViewCount:保证ScrollViews控件有且仅有一个子视图
    • ShowToast:查找创建Toast对象但没有调用show()方法的代码
    • 其他
  • 安全性(Security)
    • ExportedActivityActivityexportedtrue时,设置一个Permission,让使用者获取了Permission才能使用
    • SetJavaScriptEnabled:不确定你的程序中确实需要JavaScript就不要执行SetJavaScriptEnabled()方法
    • 其他
  • 可用性(Usability)
  • 性能(Performance)
  • 可访问性(Accessibility)
  • 国际化(I18n)

限于篇幅,这里只做简单介绍,想要了解更多详细的Android Lint规则,可参考lint-checks文档。

自定义Android Lint规则

一般情况下,默认的这些规则已经足以满足开发的需求,但是在某些情况下,如默认的Android Lint规则可能无法满足团队特定需求、存在一些检测缺陷或者缺少一些我们认为有必要的检测等等,可能还需要我们自定义Lint规则。

关于如何自定义Lint规则,可参考ht-lint项目。它是一个自定义Android Lint规则的库,对Android Lint规则进行了扩展,在不影响Android Lint原有的检查项目的基础上进行额外内容检查。示例规则如图:

目前ht-lint库定义的规则有2类:

  • 代码规范
    • 不能直接使用Log,ToastHandler
    • throw抛出异常的时候需要把已经捕获的异常一并带上
    • Message必须采用Message.Obtain()方法获取
    • 嵌套For/If/Try最大深度是3
    • 其他
  • 资源文件命名规范
    • ActivityFragment对应的布局文件,命名要带上对应前缀。如activity_mediapicker.xmlfragment_tab.xml
    • 布局文件中控件命名要前缀表明类型。如View-->vButton-->btnTextView-->tv等等
    • viewholder关联的布局资源以item为前缀。如item_shopping_cart.xml
    • 其他

构建项目时,执行gradle任务lint后,可以通过Jenkins的插件Android Lint Plugin来收集、整理以及显示lint的检查结果。我们在通知邮件中仅显示摘要信息:

点击邮件中的链接地址,会跳转到Lint Issues包含图文描述的界面,如图:

2. FindBugs

Findbugs是一个静态分析工具,通过一组缺陷模式与Java字节码进行对比从而发现问题。

它定义的缺陷模式也有很多种,常见的有以下:

  • 坏的实践(Bad practice)
    • HE:类中equals()hashCode()没有同时定义,或者使用了错误的对象的hashCode()equals()
    • DE:方法终止或不处理异常,一般情况下,异常应该被处理或报告,或被方法抛出
    • 其他
  • 多线程正确性(Multithreaded correctness)
    • ESync:空的同步块,很难被正确使用
    • No:使用notify()而不是notifyAll(),只是唤醒一个线程而不是所有等待的线程
    • 其他
  • 具有潜在危险的代码(Dodgy)
  • 易受攻击代码(Malicious code vulnerability)
  • 国际化(Internationalization)
  • 正确性(Correctness)
  • 性能(Performance)

这里只做简单介绍,更多缺陷模式的定义可参考FindBugs Bug DescriptionsFindBugs也是支持自定义规则的,这里就不做过多解释了。

我们可以发现,这些缺陷模式部分内容与Android Lint是有重复的,但更多的内容是其独有的,因此Jenkins-ci也对FindBugs进行了集成,提高代码检查质量。

FindBugs需要通过apply plugin: 'findbugs'的形式引入findbugs插件,并自定义一个任务如findbugs

//findbugs插件
apply plugin: 'findbugs' 
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {//依赖Debug打包所产生的class文件
    ...
    excludeFilter = file("${project.rootDir}/configs/scripts/findbugs-filter.xml") //配置过滤文件,减少不必要的检查
    classes = files("${project.buildDir}/intermediates/classes/")//默认分析的class文件对象
    ...
}

执行findbugs任务之后,通过FindBugs Plugin可以获取检测结果并显示。最后,在邮件中显示检查的摘要信息如图:

点击邮件内链接地址,同样跳转到FindBugs检查结果的图文详情界面。

3. FireLine

火线FireLine)提供一种静态代码扫描服务,基于PMD开源。FireLine的检查规则不是很多,目前只是包括安全、日志、内存和基础四类规则:

  • 安全类
    • 不合理的Activity组件导出会导致拒绝服务
    • AndroidMannifest.xml文件中allowBackup设置为true时会导致数据泄露
    • 其他
  • 日志类
    • Log中不要输出敏感信息,例如piduidimei号等
    • 不要在Log方法中对变量进行赋值操作
  • 内存类
    • Stream对象关闭失败,打开的资源对象需在finally中关闭
    • Stream对象因异常未关闭,打开的资源对象需在finally中关闭
  • 基础类
    • 循环时避免混乱的使用增量参数
    • 避免从finally块中返回,这会导致异常捕获后又被抛弃
    • 其他

FireLine的许多检查规则看上去更像是项目最佳实践规则,与Android LintFindBugs相互补充,不断完善。因此在自动构建中进行了集成。

FireLine对外提供一个jar包,必须通过命令行的形式运行,因此需要添加如下的gradle任务:

//扫描java源码,需指定扫描对象以及结果存储位置
task fireLine << {
    def fireLineDir=env.JENKINS_HOME+"/jobs/"+env.JOB_NAME+"/builds/"+env.BUILD_NUMBER+"/"
    exec {
        workingDir './'
        //命令行执行jar包
        commandLine "java", "-jar", "${project.rootDir}/configs/jars/fireline.jar", "scanSrcDir="+env.WORKSPACE, "reportSaveDir="+fireLineDir,"reportFileName=fireLineResult","user=netease"
    }
    ...
}

不过可惜的时,Jenkins中还没有专门的插件来显示FireLine的检查结果,因此要显示fireLine生成的静态html页面还需要通过插件HTML Publisher Plugin进行支持。

构建后,FireLine检查结果在邮件中显示如图:

点击链接地址,跳转到FireLine的详细报告界面:

Apk方法数、大小以及资源文件分析

随着项目的不断迭代更新,android应用不得不面对64k方法数限制、Apk体积不断变大的问题,并且这类数据人工统计也不方便。方法数限制一般是由于自身代码逻辑太复杂或者引入的第三方库导致的,而Apk的过大主要由so文件和资源文件导致,如涉及Android屏幕适配,资源文件就特别多。因此,本次实践中将对该类数据进行收集分析,更加直观的展现给关注该类信息的用户。

1. Apk方法数、大小统计

Apk方法数统计集成中,采用了开源的Gradle插件项目-dexcount-gradle-plugin,该插件会根据配置为打包的每个Apk文件生成一份方法数和字段统计的文件:

buildscript {
    repositories {
        mavenCentral() // or jcenter()
    }
    dependencies {
        classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.1'
    }
}

// make sure this line comes *after* you apply the Android plugin
apply plugin: 'com.getkeepsafe.dexcount'

配置完成后,执行assemble任务时,会在输出目录下生成apk方法数统计数据文件:

最后,为了在jenkins中使用该数据,创建一个apkMethodCounts任务将上述的所有的json格式文件进行统一解析转换,并将解析结果和生成的Apk文件--对应起来。构建结果在邮件中显示如图:

2. 资源文件大小统计

资源文件统计插件resource-size-plugindexcount-gradle-plugin的使用方法基本一致,该插件也是需要通过动态添加。

插件配置完成后,执行resourcesize任务,会在输出目录下生成分析结果resoucesize.txt。其中,第一行表示所有资源文件的总大小,后面每行代表单个文件最大的文件名称以及文件大小:

最后,将数据进行简单处理,构建结果在邮件中显示如图:

Git Changelog分析

随着项目的迭代,代码的提交越来越频繁,代码的管理的重要性越来越突出,代码提交的规范性越来越受到重视。在没有规范的情况下,提交的日志信息可能如图:

尽管日志对项目的正常推进不会构成太大影响,但也带来了一些不可忽视的问题:

  • 代码提交频繁且不规范,导致日志信息多而乱
  • 只对某些日志感兴趣,如Bug修复,查找不便
  • 一些重要代码变更提交后,需要借助工具通知相关人员
  • ...

因此,在参考了一些Git规范的基础上,整理出一份可行的Git提交规范。其规定了完整的git提交日志由信息头部、信息主体和信息尾部构成,其中信息头部需要包含类型、范围和主题三类信息。

<type>(<scope>):<subject>
 空行
<body>
 空行
<footer>

Fix/Close #bug号,#bug号,...

从长远来看,日志的规范性是很有必要的,它不使得日志信息更加容易检索、阅读,而且日志信息的可利用性也大大提高。我们完全可以针对日志中的某些类信息进行提取转化,进行有效利用。

因此,在本次集成中,我们就对提交日志中的featbug fix两类信息进行了收集处理。

Git提交规范随便不复杂,但是在编写的过程中可能一个不留神就会导致日志不够规范。因此,这里也给大家推荐一个撰写合格日志的工具--Commitizen,很好用,推荐~。使用该工提交代码之后,会自动生成上述格式规范的日志信息。

通过Commitizen工具(或者手动)提交之后,生成的Commit message如图:

从图中的示例可以看出,被处理的日志信息的类型是featurebug fix的两类。在构建的过程中提取出这些日志信息后,进过简单的整理、转化,就可以展示给用户。

到此,关于自动化构建内容定制的部分已经介绍完了~后续可能继续对定制内容进行扩展,敬请期待~

Jenkins与Docker结合实现快速配置

上一节仔细讲解了自动化构建内容定制,其中涉及到很多的插件、工具,一个一个配置起来很麻烦,而且配置过程中还有很多坑。不过大家不要慌,我们已经为大家考虑到这点了,提供了解决方法,那就是使用Docker容器。基于Docker轻量级、可移植的特点,本次实践将Jenkins-ci的构建环境配置打包到Docker中,从而实现快速配置。

Jenkins集成镜像的内容主要有4个部分:

  • Java运行环境
  • Git运行环境
  • Android编译环境
  • Jenkin运行环境

目前,该镜像已经上传到网易云计算基础服务

可通过一下命令获取:

docker pull hub.c.163.com/netease163/ht-jenkinsci:latest

更多内容可参考网易云计算基础服务使用指南


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者付光鑫授权发布。