安卓 DevOps:从一次推送命令到生产
DevOps 是一种广为人知的活动,其主要目的是使软件交付自动化。的确,DevOps 的目标是持续测试、代码质量、功能开发和更轻松地进行维护更新。因此,DevOps 的终极目标之一是让开发者可以执行快速可靠、自动化的发布,理想状态下,整个流程都不需要人为操作。这被称为持续交付。撰写本文的目的是展示我们现在也能在安卓上实现这一目标,同时分享笔者的想法和反馈意见。
以持续集成为起点
为了实现持续交付,必须确保强大的持续集成。这已经在安卓环境实行一段时间了,但是为了清楚起见,咱们还是回顾一下吧。
首先,任何安卓应用程序都应具备持续集成。是的,笔者就是这个意思。实际上,这为现代应用程序开发带来了不可忽视的几个好处。在笔者看来,最大的优点是以下几项:
-
构建自动化:告别“但是在我的机器上可以构建成功”。应用程序在哪里都能构建。
-
尽早试错:每次推送后都进行构建可以确保尽早发现错误。
-
测试持续化:确保测试始终进行
-
持续打包:在打包二进制代码时避免人为错误。
-
发布速度更快:因为我们对每一步构建都有信心,发布也变得更加简单。
-
信心增强:终于,我们可以信任自己的代码和流程,并且减少意料之外的错误。
典型的持续集成过程
First, we need an integration server like Jenkins or Travis. The following jobs are my standard configuration:
首先,我们需要一个 Jenkins 或者 Travis 那样的集成服务器。以下作业是笔者的标准配置:
• 一旦源码存储库(Git、SVN等等)中的推送完成,就会开始一个作业。它会查看 dev 分支、编译代码、运行单元测试,并打包调试 APK。
• 第一个作业成功完成后,运行下一个作业。它会运行集成测试(通过Espresso 或 Robotium),通过重现场景和检查图像内容来确保用户体验。你可以使用连接设备(如果你的 CI 服务器不容易获取的话,就有些困难)、Genymotion 或随 Android Studio 2.0 发布的最新内置模拟器(快去试试吧!)来操作。
• 一个作业会运行代码测量(举个例子,通过 Sonarqube)以监测代码质量。例如,笔者会在每天半夜运行这个作业。
• 最后,在我们推送 master 分支或发布分支后运行一个作业。它会编译代码,并生成发布 APK。
好啦!如你所见,操作非常简单,而且能保证笔者之前列举的所有优点。
测试是关键
笔者曾写过一篇关于安卓测试的文章。测试非常重要,因为它是能自动证明我们的应用按计划运行的唯一方法。有很多工具可以帮我们写出优秀的测试代码,所以要明智地进行选择。
同时,在选择集成到应用中的函数库时要实事求是。实际上,你应该明白如果函数库的测试覆盖率较高,测试应用程序就会更轻松。他们已经考虑到如何正确测试和通过测试促进开发的问题了(IMO、OkHttp 和 Retrofit 都是很好的范例)。相当于你在使用的同时就进行了测试。
最后,类似 Dagger 的函数库可以增加可测试性。实际上,它会迫使你遵照单一职责原则,并且将代码正确分离,这样测试就更加容易。
一旦你拥有了强大的持续集成,我们来看看怎么升级吧。
持续交付:无限升级
举个例子,在 Captain Train,我们每6周发布一个新版本,并且对此非常谨慎。目前:
• We have a beta phase.
• We support 4 locales.
• We support 3 types of device (phone, 7 and 9 inches tablets).
• We always check our Play Store listings.
• We create release notes.
• We use the rollout feature.
• We upload our 72 screenshots (6 screenshots * 4 locales * 3 types)
• We have a wear companion.
- 我们有一个测试阶段。
- 我们支持4 种语言环境。
- 我们支持3种设备(手机、7寸和9寸平板电脑)。
- 我们时刻关注在谷歌电子市场的排名。
- 我们创建发布说明。
- 我们采用发布功能。
- 我们上传72张截图(6种截图X4中语言环境X3中设备类型)。
- 我们有一个穿戴伴侣。
这个流程需要花时间,而且不止一点儿。最近,我们决定是时候尝试把这个流程自动化了。虽然主要目的是为了减少发布版本需要的时间,但是如果同时也能避免人为错误,保持各个发布版本之间的连贯性,就更好了。这也让我们担负了重大的责任,因为开发者控制了整个发布流程。实际上,市场和传播团队也要跟我们一起看看怎么将他们的变化集成到发布版本中去。
但是坦白说……在安卓,开发者并不能控制所有的事情。谷歌可以。但是,它提供了 HTTP API,使开发者轻易地与谷歌电子商店控制台互动。他们还提供了各种开发语言的客户端,例如 Java(这是必须的!)、Phython、Ruby,等等……
在本文中,笔者将主要讨论 Java 客户端,因为这很可能是安卓开发者最了解的开发语言。
编写你自己的 publisher
让我们看看如何编写定制化的谷歌电子市场 publisher。分为两步:首先,我们要配置控制台,让客户端能够操作,然后探索 API。通常跟谷歌建立连接时最困难的就是配置……相关文档可以在这里找到。请注意,文档也许不是最新版的。
配置
首先,如果没有现成的,你需要在谷歌控制台新建一个项目。接下来,我们需要启用 Google Play Android Developer API
。
Once it is done, we must create a credential of type Service account key:
完成之后,我们必须创建一个 Service account key
类型的证书:
最后,填写小表,下载 JSON 格式的证书文件。你需要保存三个值:private_key_id
、private_key
和 client_email
。把 private_key
的值保存到它自己的 secret.pem
文件。
开发者控制台搞定了……现在我们来看第二个控制台吧!
连接到你的电子市场控制台。你需要依次访问 Settings
> API access
:
接下来,你只需要连接你的项目。最后在 Service accounts
,授权你在 JSON 文件的 client_email
处填写的邮箱地址。
现在好了。一切搞定!
探索 API
现在让我们通过 Java 客户端访问接口。我们在 publisher 上面创建一个单独的 Java 项目,并且添加以下依赖项(在 maven central 有):
compile 'com.google.apis:google-api-services-androidpublisher:
v2-rev20-1.21.0'
下一步是新建一个 AndroidPublisher
。首先,我们通过以下信息来实例化 GoogleCredential
:一个客户端连接,一个 JSON factory,一个与 private_key_id
对应的私有关键 ID,一个与 client_email
对应的账户 ID,ANDROIDPUBLISHER
范围,以及与 private_key
对应的、包含私有关键信息的重要文件。是的,所有这些都需要。
然后,我们用应用程序包创建一个 AndroidPublisher
例子。
http = GoogleNetHttpTransport.newTrustedTransport();
json = JacksonFactory.getDefaultInstance();
Set<String> scopes =
Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER);
GoogleCredential credential = new GoogleCredential.Builder().
setTransport(http).
setJsonFactory(json).
setServiceAccountPrivateKeyId(KEY_ID).
setServiceAccountId(SERVICE_ACCOUNT_EMAIL).
setServiceAccountScopes(scopes).
setServiceAccountPrivateKeyFromPemFile(secretFile).
build();
publisher = new AndroidPublisher.Builder(http, json, credential).
setApplicationName(PACKAGE).
build();
AndroidPublisher 是谷歌 API 的主要入口。它有一个 edits 方法,可以让我们修改想从控制台获取的数据。
要开始一个新版本,你必须用 insert 请求来开始,然后保存它的 id,接下来每次调用都会使用该 id。
AndroidPublisher.Edits edits = publisher.edits();
AppEdit edit = edits.insert(PACKAGE, null).execute();
String id = edit.getId();
现在,我们可以开始修改控制台数据了。举个例子,如果你想修改排名:
Listings listings = edits.listings();
Listing listing = new Listing().
setFullDescription(description).
setShortDescription(shortDescription).
setTitle(title);
listings.update(PACKAGE, id, "en_US", listing).execute();
You can also upload screenshots:
也可以上传截图:
Images images = edits.images();
FileContent content = new FileContent(PNG_MIME_TYPE, file);
images.upload(PACKAGE, id, "en_US", "phone5", content).execute();
最后一个例子,上传一个 APK:
// APK upload
Apks apks = edits.apks();
FileContent apkContent = new FileContent(APK_MIME_TYPE, apkFile);
Apk apk = apks.upload(PACKAGE, id, apkContent).execute();
int version = apk.getVersionCode();
// Assign APK to Track
Tracks tracks = edits.tracks();
List<Integer> versions = Collections.singletonList(version)
Track track = new Track().setVersionCodes(versions);
tracks.update(PACKAGE, id, "production", track).execute();
// Update APK listing
Apklistings apklistings = edits.apklistings();
ApkListing whatsnew = new ApkListing().setRecentChanges(changes);
apklistings.update(PACKAGE, id, version, "en_US", whatsnew).execute();
笔者鼓励你多多探索这个接口。它既强大,又不复杂。
最后一步,你必须提交自己的版本。实际上,谷歌会记录你的每一个更改请求,但是只有在你提交版本之后,这些更改才会被保存。笔者同时建议你在提交之前先验证这些更改。
edits.validate(PACKAGE, id).execute();
edits.commit(PACKAGE, id).execute();
如你所见,你在代码开头找回的 id 可以被当做事务 id。通过调用 insert,update/upload
你的更改以开始事务,validate
以及最终 commit
。非常简单。
结论
现在,安卓应用程序也可以呼应 DevOps 活动,拥有强有力的持续交付了。在 Captain Train,我们选择编写自己的 publisher 工具来确保自己控制每个步骤和重要步骤的字节。一旦发布任务成功完成,我们就把它当做一个脚本来运行。不过也有 Jenkins 插件或 Gradle 插件可以帮你完成这些工作。只是要注意了解幕后的运行情况。毕竟你在处理生产。必须小心谨慎。
这样的工具和流程让你只需推送 master 分支即可在生产环境中发布新版本。既简单、快捷,又可靠、节约时间。
显然,在一个应用程序中可行的方法不一定适用于所有的团队、公司和应用程序。还要依靠你的团队以及团队与市场传播团队的关系。不过,在此笔者想要总结的是,持续交付应该成为每个开发团队的目标,往这个目标迈进的每一步都是一种胜利。
OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客