全自动化的 Android 编译管线
【编者按】Nicolas Frankel 是 hybris 的高级顾问, 在Java / J2EE 领域拥有超过10年的管理经验,本文阐述了他在使用自动化工序去构建 Android 应用程序遇到的一些难题,大家不妨读读,希望能有所收获。
以下系译文:
在我目前的工作中,我必须使用一些自动化工序去构建 Android 应用程序。这篇文章的目的就是描述我所遇到的难题,避免读者在这个过程浪费更多时间。目的就是分享我在这一过程中所遇到的困难,为读者提供前车之鉴,从而节省宝贵的时间。
环境搭建如下:
- 使用 Puppet 搭建基础设施
- 使用 Jenkins 搭建 CI 服务器
- 工程文件
- 来构建主体
- 作为主要测试工具
Puppet and Jenkins

事实上,我的准备工作已经相当完备。同事们已经使 Jenkins 服务器可以自动安装,以及准备好了所需的软件包——包括 Java 和已提供的可复用的 Puppet 类。Jenkins 的工作完全依赖于一个单一 config.xm 文件,即不同部分的封装。每部分都由一个专门的模板处理。因此,在我看来,创建一个简单的 Gradle 任务就如同在公园里散步一般轻松,最多几天时间便可以完成。
第一步非常容易:只需一个最新版 Puppet 清单,能帮助你添加 Gradle 插件到 Jenkins 服务器。
The Gradle wrapper
如果你是我博客的忠实读者,那你大概知道我对 Gradle 的看法。不过,我必须得承认,Maven 的确缺乏这种兼容性,即不论安装哪个版本的工具都确保编译成功——虽然它应该具备该功能。为了实现这一目标,Gradle 通过提供一个 JAR、一个 shell 脚本和一个属性文件,属性文件还包含了从 URL 到 Gradle ZIP 的分发,组装成所谓的包装机制。这三个需求都被存储在 SCM 中。
然而这正是麻烦的开始。在一个企业环境中进行下载,意味着要通过和验证代理。最简单的选择莫过于在工作配置下设置好一切,包括代理凭证。然而,从安全的角度来看,这样的做法并不理想,因为任何人访问 Jenkins 接口或文件系统,都能够读取这些凭据。显然,我们需要一个更好的方式。
用户已经拥有了配置代理完备的 Nexus 库。上传所需的 Gradle 分布,并更新指向它的 gradle.properties,简直易如反掌。
The Android SDK

Android SDK 只是一个 ZIP 文件。我用同样的方法:先下载文件然后将其上传到 Nexus。这一步之后,一个 Puppet 脚本会负责下载、提取,并为它设置正确的权限。
然而,事情并没有想象的那么简单。Android 开发者都知道,Android SDK 需要手动操作:开发者必须手动检查所需平台和工具,并将其下载在本地文件系统。这看上去很简单不是吗,但如果转为自动操作则会让很多开发者头疼,尽管有一个命令行相当于可以通过 SDK「带有 --no-ui 参数」来安装/更新包。如果你想了解更多,请点击这里。
谷歌工程师未能提供的两个重要参数:
- Proxy credentials – login/password
- Accepting license agreements
为了解决这一问题,网上有很多蹩脚的方案,最诱人的应该要数配置文件了,但我却发现它们没多大用。然而,通过 expect 命令的使用,我反而发现了一种创造性的解决办法。Expect 是一个漂亮的命令,用来读取标准输出,并用标准输入进行相应的填写。值得一提的是,它竟然还可以接受正则表达式。所以,在请求代理登录时,你键入登录名、填写密码,当它要求许可证接受时,你键入「同意」就能轻松搞定。虽然我反复多次试验,历经很多错误,才达到预期结果。但这个方法非常简单、直接。
我最初的设计是,使所有可能用到的装有 Puppet 的 Android 包,成为服务器配置的一部分。在标准化操作中「如文件创建或系统包安装」,Puppet 可以确定这项配置是否必要。例如:如果某文件已经存在,那就没有必要再重复创建了。在最后的 Log 中,Puppet 会报告它执行的每一个操作。起初,我试图通过在配置过程中,人为地告诉 Puppet 哪个包是已配置的,因为 Android SDK 为每个包都创建一个文件夹。但要命的是,Puppet 只接受单一文件夹来验证。对于某些包来说,并没有任何版本信息「例如 Google 游戏服务」。
因此,一个同事提出将 Puppet 配置的更新,移动到每个任务的预先步骤中。这样能修复非幂等问题,同时,还能在每个任务中更新所需配置。
Robolectric

说到这里,我本以为一切都搞定了。但非常不幸,并不是这么回事,就因为这个库—— Robolectric。
此前,我对 Robolectric 没什么了解,只知道这是一个测试库,能够在 Android 上运行测试,而无需任何物理设备的连接。在 Jenkins 上试图编译时,我偶然发现了一个「有意思」的问题:尽管 Roboletric 提供了一个具有完整依赖性的 POM, 但 MavenDependencyResolver 类硬编码库应该从哪里下载?
唯一的解决办法是通过扩展上面的类来实现。我用的就是上面提到的企业 Nexus 库。
上传并发布任务
为了实现前面的任务,我只需添加一个自定义 Gradle 任务,从 settings.xml 得到 Nexus 设置「由 Puppet 调配」。基于此,我成功地上传了任务。最后,对于每个任务执行的型号,我添加到所上传的工件集的输出文件中。因此,不管编译文件是哪种型号配置,下面的命令都将只上传 XXX 和 YYY:
./gradlew assembleXXX assembleYYY upload
上面的任务都搞定了,那发布岂不是更简单:唯一需要的就是设置 Gradle 插件,它添加了一个发布任务,类似于 Maven 的 deploy。
结束语
作为后端开发人员,我已经习惯于持续集成设置,毫无疑问,我可以在几天内搞定 Android CI 的进程。对于 Android 系统在 CI 上的欠缺,我觉得不可思议。每一步都苦不堪言,糟糕的记录「如果有的话」和解决方案似乎更像黑客般具有破坏性。如果你想沿着这条路走下去。呐,别说我没告诉你……Good Luck!
原文链接: Fully Automated Android Build
本文系 OneAPM 工程师编译整理。OneAPM 是应用性能管理领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客。
全自动化的 Android 编译管线的更多相关文章
- 6.6 Android 编译机制的变迁
我们使用Java开发android,在编译打包APK文件时,会经过以下流程 Java编译器将应用中所有Java文件编译为class文件(JVM运行的是.class文件,而DVM是.dex文件) dx工 ...
- [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程
本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...
- 【Linux】CentOS下升级Python和Pip版本全自动化py脚本
[Linux]CentOS下升级Python和Pip版本全自动化py脚本 CentOS7.6自带py2.7和py3.6 想要安装其它版本的话就要自己重新下载和编译py其它版本并且配置环境,主要是软链接 ...
- Android编译优化系列-kapt篇
作者:字节跳动终端技术---王龙海 封光 兰军健 一.背景 本文是编译优化系列文章之 kapt 优化篇,后续还会有 build cache, kotlin, dex 优化等文章,敬请期待.本文由Cli ...
- Android编译环境折腾记
题记:感觉是时候写点什么了=_=! 第一次安装了ubuntu14.04.5,官网下载的iso,官网下的jar,编译android4.x需要安装jdk6,更高的版本会有问题,baidu到很多搭建环境的步 ...
- Cocos2d-x3.6 Android编译问题
在Cocod2d-x论坛上看到越来越多人吐槽新版本更新太快,改动太大,而且经常有BUG导致升级要折腾很久很久..但我就是喜欢折腾,喜欢升级到最新版本,看看有了哪些新功能,哪些改进.为此也折腾了不少,遇 ...
- Xamarin.Android编译CPU类型选择方式
Xamarin.Android编译CPU类型选择方式 在Xamarin.Android编译的时候,默认提供了5种CPU类型供大家选择.它们分别为armeabi.armeabi-v7a.arm64-v8 ...
- Xamarin.Android编译提示找不到mscorlib.dll.so文件
Xamarin.Android编译提示找不到mscorlib.dll.so文件 错误信息:AOT module ‘mscorlib.dll.so’ not found: Cannot load lib ...
- Android编译过程详解(一)
Android编译过程详解(一) 注:本文转载自Android编译过程详解(一):http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359 ...
随机推荐
- 算法系列8《Base64》
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,Base64编码可用于在HTTP环境下传递较长的标识信息.在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单 ...
- ios各种手势,很有意思
转自http://blog.csdn.net/likendsl/article/details/7554150 这哥们很厉害的 一.概述 iPhone中处理触摸屏的操作,在3.2之前是主要使用的是由U ...
- WebApi接口访问频率控制的实现
关于限流的文章,博客园内还是有挺多的.本文做了一个基于Filter限流的例子,算是对WebApiThrottle使用的一个具体的实例. 实现方法: 1.使用Nuget,对WebAPI项目添加WebAp ...
- 2.2孙鑫C++
1.继承 动物有 吃 睡 呼吸的方法 当然 鱼也有 不用重复再定义 1)public 那里都可以访问 #include <iostream.h> class Animal //类 基 ...
- Introduction to Haskell
"I know why you're here. ...why you hardly sleep, why night after night, you sit by your comput ...
- python中fork()函数生成子进程分析
python的os module中有fork()函数用于生成子进程,生成的子进程是父进程的镜像,但是它们有各自的地址空间,子进程复制一份父进程内存给自己,两个进程之 间的执行是相互独立的,其执行顺序可 ...
- 使用getElementById获取xml中的指定元素
Document有一个getElementById的方法,在文档中的解释是: 返回具有带给定值的 ID 属性的 Element.如果不存在此类元素,则此方法返回 null.如果多个元素具有带该 ...
- 课题练习——找从1到N出现的1的个数
#include<iostream.h>#include<conio.h>int Sum1(int n){ int count = 0; //记录1的个数 int factor ...
- Github的使用以及Git的简单入门 - 课程作业三
GitHub创建项目 登录GitHub,在个人主页创建项目(repository) 创建后会生成2个文件,README.md和.gitignore.如图 创建本地仓库 如果是第一次使用git的话,需要 ...
- JS多种方法实现随机颜色;
JS随机颜色有很多地方要用到:比如大家看到很多标签连接都是五颜六色.实现随机颜色的方法有多种,下面来看看具体的实现代码: 方法一: var getRandomColor = function() { ...