打 debug 包流程:
- git pull 分支最新代码
- Android Studio:Build - Generate Signed APK
从 IDE 里可以看到,实际上该操作是执行了 assembleDebug,在打包完成后再将编译目录下的 apk 包安装到调试的手机上并运行。
- 拷贝了工作目录下 app/build/outputs/apk 文件夹下的 apk 安装包交付测试
显而易见这些是很枯燥、重复且浪费时间的工作,而一切重复的工作皆可自动化。我们选择使用 Jenkins 这一已经很成熟的持续集成方案来实现自动化打包。
初识 Jenkins
What is Jenkins?
Jenkins is a self-contained, open source automation server which can be used to automate all sorts of tasks related to building, testing, and deploying software.
Jenkins 说白了其实就是后台服务加上 web 管理配置页面。Jenkins 里,每个任务称为 Job,所以简单的说,持续集成的实质就是通过 web 页面构建一个或多个任务,然后通过后台服务持续、自动地执行这些任务。
安装教程
因为 Jenkins 是基于 Java 开发的(但不仅限于构建基于 Java 开发的软件),所以运行 Jenkins 需要配置 Java 环境。
- 下载 Jenkins 安装包。Jenkins 下载地址。虽然官方也提供了各个平台的安装包(例如 Mac 端 的 pkg 文件,但个人认为直接下载 war 包自行使用命令安装更为方便)
- 在安装包所在目录运行命令:
//默认运行在8080端口
java -jar jenkins.war
//如果报错误为端口号已被占用,先查看端口号(例如8081端口)被什么程序占用
lsof -i tcp:8081
//记住服务的 PID 号(例如8123),强制 kill 相应服务
sudo kill 8123
//或直接指定未被占用端口运行
java -jar jenkins.war --httpPort=2333
Jenkins 就启动成功了。
- 浏览器中进入 Jenkins web 配置页面:http://localhost:8081。
第一次启动时在控制台会输出一串密钥用于首次登陆验证。当然,如果懒得翻控制台打印出的命令,也可以直接到本地的 Jenkins 安装目录去找(Mac 上一般为 用户目录下的/.jenkins/secrets/initialAdminPassword)。该密钥同时也是 Jenkins 初始账号 admin 的密码。
输入密钥之后会出现如下让用户选择安装插件的选项。如果是第一次接触的同学可能会有点懵。所以这里建议点击右上角关闭,直接进入 Jenkins。后续再根据需求安装插件。
构建 Job
准备工作
成功进入 Jenkins 后,我们就可以开始构建任务啦。但是在此之前,还需要为 Jenkins 配置相应的环境,以及安装需要用到的插件。
安装插件
左侧选择系统管理 - 插件管理 - 可选插件,搜索并安装插件。
必装插件:
- Gradle Plugin
- GitLab Plugin
建议安装的插件:
- build-name-setter (用于自定义每次构建的名字)
- Build Timestamp Plugin (比较好用的时间戳插件)
tips:如果插件管理里显示可更新、可选插件都为空,前往插件管理 - 高级 - 滑动到最下方的升级站点,将地址替换为http://mirror.xmission.com/jenkins/updates/update-center.json
。
配置环境
还需要配置一下环境变量,在系统设置 - 全局变量里添加一个 Android Sdk 的目录。
- ANDROID_HOME = android sdk 所在目录
配置 GitLab 服务器
在配置服务器之前,需要先配置一下证书。
左侧菜单 Credentials - System - Add domain 添加一个证书,Domain Name
输入名字后点击 OK,再点击 adding some credentials 添加有访问代码权限的账户的 token,Kind 栏选择 GitLab API Token,Api token 一栏填入账户的 token,token 的获取方式为 GitLab - User Settings - Private Token。
接着配置一下服务器。左侧 系统管理 - 系统设置,安装 GitLab 插件之后会看到新增了一栏 GitLab,在这里配置一下源码服务器。
Host Url 填写服务器地址:http://xxx.xx.xx.xxx:9000/。
Credentials 选择刚才添加的证书。然后点击右下方的 Test Connection,不出意外的话就可以连接成功了。
添加 SSH
配置好服务器后,还需要配置用于访问服务器的私钥 。SSH 协议规定远程主机在用户发起请求后,会发送一串随机字符串回来,用户使用本地存储的私钥对字符串进行加密后又发送回来,远程主机再使用用户事先配置的公钥比对加密后的字符串,以此来鉴定本次访问是否可信。因此,用于获取源码的服务器上必须配置用于远程登录服务器的 SSH 私钥。
左侧菜单 Credentials - System - Add domain 添加一个证书,Domain Name
输入名字后点击 OK,再点击 adding some credentials 添加有访问代码权限的账户的私钥,Kind 栏选择 SSH username with private key,Private Key 栏有多种方式可选,这里选择直接输入私钥,选择 Enter directly,直接复制私钥内容到输入框中(私钥位置:用户目录/.ssh/ 下的 id_rsa 文件,直接复制文件的所有内容),其他都不用填,点击保存。
构建第一个任务
点击左侧菜单栏 - 新建,新建一个任务
这里看到了两个选项。多配置项目,顾名思义,更适用于有上下游任务、多个项目联动的任务,该项目相比自由风格的软件项目新增了些可选配置,例如上游项目正在构建时阻止该项目构建、使用自定义工作空间等。
我们先选择自由风格的软件项目来试试手。输入项目名后可以看到整个配置页面了。最上方 GitLab connection 一栏选择刚配置的 GitLab。源码管理一栏选择 Git,配置一下项目地址。
Repository URL 填写项目地址:git@git.xxxx.com:xxx/xxxx.。
Credentials 一栏选择之前添加的 SSH。
Branches to build 一栏填写从哪个分支获取,例如从主分支:*/master
配置好之后,滑到最下方应有并保存。之后点击左侧菜单 - 立刻构建试一下吧。如果看到任务被成功构建那就说明已经成功从服务器拉取源码啦。
编译项目
进入 Job,左侧菜单 - 配置,在构建一栏增加构建步骤,选择 Invoke Gradle Script - 选择 Invoke Gradle,并在下方的 Tasks 中配置要执行的命令。比如打测试包命令:
clean
assembleDebug //如果项目中配置了 productFlavors,打包命令需要相应地更换为你需要打包的版本
配置完成后,一样的进入到项目的 app/build/outputs/apk 目录下就可以看到打好的 debug 包。项目位置位于:用户目录/.jenkins/workspace/xxx 。
当然,你也可以自己执行项目的工作目录。在 Job 的编辑页,滑动到最上方, General 选项卡最顶端点击 高级 按钮,可以看到选项:使用自定义的工作空间,例如:
目录:workspace/work/testProject
显示名称:testProject
实现自动化
常见的自动化执行配置方法:进入 Job,左侧菜单 - 配置 - 构建触发器:
定时执行
有两个比较相似的行为:
- Build periodically :周期性地执行构建
- Poll SCM :检查源码变更,如果有变更则拉取源码并执行构建
二者都需要在下方的日程表中填写定时规则。
日程表编写规则
简要介绍一下定时编写规则:
规则由5个参数组成,每个参数之间以空格间隔(单个参数里不可以包含空格),例如:* * * * * 。* 号代表忽略该参数。
五个参数从左起分别代表:分(范围0 ~ 59)、时(范围0 ~ 23)、天(范围1 ~ 31)、月(范围1 ~ 12)、星期(范围0 ~ 7,0/7均代表周日)
同个参数可以指定多个数,以英文逗号分隔(注意不要带空格),例如:�2,3,5 ;可以指定连续数:2-5(等同于 2,3,4,5)
举几个例子直观地看一下:
H 10 * * * //每天10点整点。等同于 0 10 * * * ,如果分的参数输入0,会建议你使用 H 替代。
20 8,10,18 * 11,12 1-4 //11、12两个月的周一到周四的每天8点20、10点20、18点20。
GitLab Hook
构建触发器选择:Build when a change is pushed to GitLab. GitLab CI Service URL: http://localhost:8081/xxx/xxx
这个需要在 GitLab 的源码项目里配置一下上面提到的地址,这样源码在接收到 push 事件时会通知 Jenkins 执行构建。因为没有将 Jenkins 发布到外网,所以可以看到上面提供的 hook 的地址也只是个本地的 localhost 的地址,填写这个地址外网是无法通知到的。这里暂时没有测试该特性。
基于上游项目的构建
构建触发器选择:Build after other projects are built。并填写上游项目的 Job 名称。还可以基于上游项目的构建情况,是否成功、失败来构建自身。
基本上这些操作已经可以使整个自动化打包流程运行起来了。
关闭服务
采用 Jenkins CLI的方式来关闭Jenkins。
左侧菜单 - 系统设置 - Jenkins CLI。下载 jenkins-cli.jar 之后,通过使用命令控制 Jenkins,例如:
java -jar jenkins-cli.jar -s http://localhost:8081/ shutdown //关闭 Jenkins 服务
如果执行该命令时报没有权限的错误,则需要为 admin 账号配置一下权限。
另外网络上很多提到直接在链接后加上相应的操作,例如 exit、restart、reload等,例如访问 http://localhost:8081/exit 代表退出 Jenkins,但估计很多人会遇到一下问题:
此类跳转失败的错误原因是 Jenkins 在 http 请求头部中放置了一个名为.crumb的token,在使用了反向代理,并且在设置中勾选了“防止跨站点请求伪造”之后此
token 会被转发服务器认为是不合法头部而去掉,导致跳转失败。解决方法就是左侧菜单 - 系统管理 - Configure Global Security 中取消勾选 防止跨站点请求伪造
权限配置问题
如果因为设置错误而收回了配置权限,导致 admin 账号连主页都无法进入,解决的方法就是直接到 Jenkins 安装目录里修改配置文件。
进入安装目录(用户目录/.jenkins)/config.xml,修改 useSecurity 栏目,修改为任何用户可以做任何事:
<useSecurity>true</useSecurity>
<authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
<securityRealm class="hudson.security.HudsonPrivateSecurityRealm">
<disableSignup>true</disableSignup>
<enableCaptcha>false</enableCaptcha>
</securityRealm>
参数化构建
可以在打包时指定打包分支。
安装插件 Git Parameter
安装后,在项目的设置页,选择参数化构建 - Git-Parameter,Name 随意填,例如mybranch,Parameter Type 选择 Branch。在下方的源码管理一栏中的 Branches to build 之前我们填的是指定的分支,例如 */devel。现在有了分支参数之后,我们只需要映射分支参数即可,这里修改为 $mybranch。保存设置。
现在在项目的右边可以看到 Build with Parameters 的选项。点击该选项就可以看到刚刚设置的 mybranch 里罗列出了 git 项目上所有的分支。打包时就可以选取指定的分支打包了。
Gradle 根据参数构架不同的版本,例如正式包、测试包
这里就要取消掉 invoke Gradle script,选择 Execute shell,使用 shell 脚本来实现参数化构建。
chomd +x gradlew
case $build_type in
debug)
./gradlew assembleDebug
;;
release)
./gradlew assembleRelease
;;
*)
exit
;;
esac
展望
以上的自动化打包流程实际上还有可以改进的地方,例如每次测试人员都需要从 Jenkins 服务器上拷贝 apk 安装包,这一流程也应该自动化实现,比如使用脚本自动将 apk 安装包上传至公司 FTP 服务器,或者上传至第三方软件分发服务商,例如蒲公英、Fir 等,还可以将下载地址转换为二维码显示在相应的 Job 页,测试人员直接扫码安装即可。
以及 Jenkins 的其他功能的扩展,例如构建发生错误时自动将日志发送至相关开发人员的邮箱,使开发人员可以更快更及时地响应问题。
以及如果项目有编写单元测试,可以使用 Jenkins 周期性地自动运行单元测试,及早发现新引入的问题。
Jenkins 是一个工具,同时也代表着一种思想,它告诉了我们:
懒是第一生产力 :P。
以上。