[翻译]Android开发方法学

这是Cyril Mottier最近更新的一篇文章,原谅地址在这里:Android开发方法学

这篇文章是他介绍自己所在项目小组(Capitaine Train Android Team)设计、开发时的一些流程,其实并没有什么有关Android代码方面的什么干货,但我觉得看一下仍能给我们一些APK生产过程中一些启示,比如APK的版本问题等。

下面是博客的原文,有兴趣的可以看一下:

软件开发方法学

我最近常被问到Capitaine Train的Android团队是如何工作的:“你们多久发布一次应用新版本?”,“你们的版本管理策略是怎么样的?”,“你们公测测试构建(test builds)吗?”等等。让我们先说明白:我不是一个流程学或方法学怪胎,且在大多数时间选择对此缄默。我宁愿把自己看作产品男。然而,不管产品有多大,软件开发流程学都是产品成功的一部分。本篇尝试分享和讨论Capitaine Train中的Android团队所使用的“工作方法”。

在深入了解这篇文章之前,让我们快速放弃一些不恰当的期待。的确,我觉得提及策略是肯定的,以下描述的进程学和其它的方法学远非完美。就像在UX和开发中没有完美的答案一样,处理大型项目也没有完美的方法。换句话说,本文无意迫使你转向新的方法学,反而,它应该被当作简单的回馈来阅读,当然,是有关团队如何管理像Capitaine Train Android应用一样的应用。

Capitaine Train对于Android 环境条件

方法学在脱离了使用的环境条件时是没有任何意义的。因此我想念,简要介绍一下Capitaine Train中的Android团队是必要的。这个团队诞生于2013年3月份,那时我加入Capitaine Train来带领Android应用。习惯极端复杂的欧洲培训生态系统花费了我相当一段时间。我们快速地决定使该团队发展壮大。2013年11月份的时候Mathieu Calba加入,之后在2014年10月份Flavien Laurent加入。如果你真正地擅长算术的话,你本已注意到Capitaine Train的Android团队只是个3人小组!为了更好地理解我们的流程学,要记住的重要一点是,成立这样一个相对微小的团队。

我十分自豪成为Capitaine Train的一部分,但更自豪的是,我新手建立了Capitaine Train中的Android团队。Mathiue和Flavien每一天都会使我感到惊喜,与之共事问题一件人生快事。所有的成员都绝顶聪明,且极度专注于产品,毫不犹豫地为自己的信仰而战,且对优良的UI/UX(并不真的跟开发人员理解地相同)有清晰的理解。换句话说,在涉及到重新设计新特性时,团队的每一个成员都极端自立。他们的大多数时间都花费在Android相关的特性上。似乎我们全都工作在Android framework上面,但有时也花费些时间在别的平台上(例如Ruby on Rails)来实现特别是跟Android有关的特性(Google Now就是一个极好的例子)。

从产品的视角来看,Capitaine Train中的Android可以总结为两种不同的应用。最重要的部分,即手持设备,适配了平台和手机。最小也是最新的部分,则以可穿戴设备为目标。可支持的最小版本是Android 4.0,且应用绑定了四种不同的语言:英语,法语,德语和意大利语。

Capitaine Train的目标消费者是全球范围的。然而,我们的产品很清晰地专注于欧洲火车。由此,我们的大多数观众以欧洲为基础,且生活在2~3个不同的时区。必须处理这样一个相对较小的时区范围是相当有益的,尤其是同步的声明新的东西作为沟通(在某各方式下,一天中的给定时刻对于所有的用户而言是近乎相同的)的工具时。

对于下载次数,我们通常不太关注。然后,在写这篇文章的时候,Google Play Store显著地表明这个应用已经下载了5~10万次。

实现流程

讨论Capitaine Train如何处理代码也许是整篇文件的主题。没有深入于设计,我将只介绍一下我们开发和测试技的流程。

谁开发,谁复查

完整的Android代码基础是通过Git管理的。我觉得本文中呈现了Git这个词是不必要的,尽管它作为最佳源码控制管理系统之一已经为众人所知。我们在Capitaine Train中广泛地使用Git,我们所有的复查技术都是基于这件令人拍案叫绝的开发工具。

在Git之上,我们也使用git flow模型(http://nvie.com/posts/a-successful-git-branching-model/)

这个模型确保了连贯可理解的commits树。简单地讲,就是开发在dev分支上完成。当队员需要开发新的特性时,一个新的“特性分支”就会从dev中创建出来。这个分支的创建取决于负责这个特性实现的队员。在这个“特性分支”完全完成之后,会重新合并到dev分支上面。发布应用和通过使用--on--off选项将dev合并到master是同步的。这个选项确保了合并操作总是使用commit来表示。最终这次commit标上了应用的版本码。在其它方面,master应该只包含涉及应用公共版本的commits。

在合并之前,代码问题应该被团队中至少两名成员阅读和检查。

因为我们的Android团队十分小,所有的特性总是由单个成员来管理的,如Bob。Bob对特性的开发完全负责:从设计到发布。一旦特性被认为成熟了,打磨好了,它将作为一个“合并请求”提交给Android团队的另外一个成员(Alice)。代码复查的完成得感谢称为GitLab的工具,它可以看作GitHub的副本。Alice负责代码复查。复查的反馈是错综复杂的。举例来说,这样的复查很普遍:“你应该使用这个方法”“我用了不同的文本颜色和大小”“如果APK大于5MB,我是不会合并的”。相较于一个简单的“No!”,拥有可选说明集合的复查通常更加合理。

Capitaine Train Android团队的特色之一是每个成员都会负责QA。确实是,公司里面没有QA团队。Bob和Alice必须确保没有回退且代码运行完美。它基本上意味着代码复查不只包含详读代码。Alice也必须测试在所有可能情况下的特性实现。

通常而言,一旦Bob和Alice对特性说了OK(代码,设计,引入的改变,API等),代码复查流程就结束了。此后,在特性分支合并到dev之前,Capitaine train Android应用的代码问题会被团队中的至少两名成员阅读和检查。这事实上适用于Capitaine Train中的所有项目。大的公司也会依靠类似的流程并且要求至少两个来自复查人员中的”+1”。很显然我们是如此小的团队,力不足以这么做啊。

打包构建

完整的项目构建在Gradle之上。新的Android基于Gradle的构建系统的主要优势事实上依赖于这个事实:它适用于开发和打包的目的。在开发一个特性的时候,我们全部都用Android Studio(也使用了Gradle);而当涉及打包的时候,我们将使用命令行。构建打包的完成得亏于我们持续地完善基于Jenkins的环境。Jenkins当前管理两个不同的Android应用:

  • Android-dev,构建处于当前dev分支上面的项目
  • Android-master,相应于master分支

当主远程repository(origin)中新的commit推送到时,这些项目的新的构建过程将会触发。主要的不同是每个工程指向不同的分支,并且拥有不同的编译选项。的确,与android-dev相反,android-master的优化和混淆得亏于Proguard。

由于我们必须创建大量的截屏(3种外形尺寸,4种语言,6种截屏:3x4x6=72个截屏),最近添加了新的自制工具到打包流程中:自动截屏(感谢Flavien)。新的工具负责按照所有必要的配置来截取屏幕,并通过统一状态栏的方式来清理截屏。在写作本文的时候,这个工具还没有集成到我们的Jenkins构建流中但这铁定了是将来要完成的事。

当谈到在Google Play Store上面发布时,一切都以手动完成。显然,新的Google Play发布API可以使用,但目前为止我们倾向于保持对版本的控制。由于我们“漫长的”发布生命周期,我确信这在我们当前处理发布的方式上面不是什么问题。

应用版本管理

管理Android应用的版本是很必要的流程。的确,Google Play Store为了侦别应用新版本使用了应用版本号。来自Google Play Store的惟一需求是确保应用版本号单调递增。

并没有尝试分辨每一个版本是主要版本、次要版本还是补丁,每一次包含至少一个新的用户可见特性的发布都被认为是主要版本。

Capitainne Train的Android应用没有使用传统的主要版本-次要版本-补丁的版本管理方式(详查http://semver.org/)。的确,由于想要尽可能少的麻烦,我们想出了一个更简单的基于两个版本数字---主要和次要---的版本管理模型。应用码通过基于这些数字利用下面的公式计算得来:

隐藏在这个版本管理策略之后的主要想法是在发布流程中没有或者有但尽可能少的摩擦。并没有抓破脑袋尝试分辨每一个版本是主要版本、次要版本还是补丁,每一次包含至少一个新的用户可见特性的发布都被认为是主要的。Bug修复和补丁问题认为是次要版本。当如下描述的发布时间表联系起来时,这种方式工作起来尤其好。

从外部用户的视角来看,只有主要版本是重要的。应用的版本名总是主要版本而不是次要版本。隐藏在这种命名策略之后的主要原因是:当次要版本并不包含任何用户可见的特性时,他们理应对用户完全透明。只使用主要版本名号使得记忆更加容易和更多的可识别度。如果你果真要了解应用的精确版本号,你可以打开Capitaine Train Android应用的“设置”屏幕。它展示了版本名(当然,也展示在了系统设置应用中)+版本号。

开发是有趣的,但你本可以使之更加有趣。我们所有的公共构建事实上内部命名的(就像常规的Android发布)。作为一个超级Stargate SG-1粉丝,我根据其中的一些角色命名每一个主要发布:ANUBIS(101),BRATAC(201),CARTER(301)等。次要发布是使用了后缀_MR<x>的名字,其中<x>是次要版本号减一:FRAISER_MR1(602),GEORGES_MR1(702).

Android Wear应用版本也同样遵循相同的版本管理模式,并依据Pixar电影角色名来命名:ANDY,BUZZ,COLETTE等。尽管从发布的视角来看,可穿戴设备应用和手持设备应用绑定在一起(可穿戴设备APK打包在手持设备APK里面并在Play Store上同时发布),我们决定使用明晰的版本管理方式。

内部玩乐显然不是维护这么一个版本号列表的唯一目的。它协助我们依据应用的当前版本轻易地改变执行行为。例如,在给定版本的应用上在硬盘上存储一些东西时遇到了严重的问题,你可能想要检查一下应用的版本是否大于存储在硬盘上的数据来获知是否是时候来更新数据到新的格式。

完整的发布流程

Capitaine Train用户可能已经注意到,这个团队非常专注于这个产品。我们并没有发布新特性,直到这些新特性完成准备好了生产。我们高质量的标准防止我们公开发布非打磨精良的特性。这是本产品没有严格的截止日期的原因之一:

没有坚持严格的截止日期,这个Android应用遵循发布火车软件发布时间表。

并没有坚持严格的截止日期,这个Capitaine Train Android应用遵循发布火车软件(http://en.wikipedia.org/wiki/Software_release_train)发布时间表。这个发布培训是基于时间的发布时间表。它既不等待特性,也不等待bug修复,但却尽可能纯粹地基于时间。简单来讲,这个应用的每一个新版本都可能被认作准时来去的火车。如果特性在按计划到来的时间准备完毕,它将跳入这辆发布火车。如果没有,这个特性将必须等待下一辆发布火车。发布火车迫使规律地引进特性,给予可预测性,允许更多常规发布。And Yes!方法学命名和公司的名字之间的相似性纯属巧合:-)

发布火车仅仅适用于Capitaine Train Android应用的主要发布。次要发布在完全不同的时间线上完成。因为次要版本通常热修复阻塞和崩溃问题,他们尽可能快地发布,而不考虑发布火车时间表。当然,这仅发生在该应用的公测阶段,这将再接下来的文章中讨论。

发布生命周期

Capitaine Train Android应用新主要版本的发布遵循递归模式。这种模式每6周重复自身一次。为什么特性是6呢?坦白地讲,这个计算后面没有复杂的数学。这仅仅是经验,源于我作为Android应用设计者和开发者的经历。这里是解释:

  • 默认情况下,Android自动更新应用。更新的时候,通知展示在通知屉中。过于频繁地发布应用可能惹恼用户,以致他认为你的应用是垃圾邮件一类的东西。
  • 过于频繁地发布应用新版本同义于小的特性更新。这使得发布减少了吸引力,更为重要的,减少了市场号召力。
  • 在另一方面,使用更长的时间周期发布也可能适得其反。用户将会完全忘记你的应用。此外,在每一次应用发布中,也增加了产生严重问题的潜在可能性。
  • 考虑到我们的开发/产品团队和产品进化的步伐,我们确信每6周就能够引进新的用户可见的特性。历史向我们展示自从版本1之后,我们设法为每一次发布坚持这个时间(但却不是版本1.0)

坦白地讲,我不认为存在完美的发布生命周期长度。在Capitaine Train,这个6周模式工作地尤其好,因为它来自用户和我们自身期待的折衷。

以上表格描述了我们的发布生命周期。就像早前预料的一样,每一个版本v(n) 都会准备7周。因为与之后的生命周期交叠一起,新版本6周发布一次。发布周期内的计划是非常灵活的,并由工程人员决定。然而,它通常缩减为:

  • 第一周与产品经理会面,讨论并评估特性优先级(例如在需求阶段、实现阶段等)。特性排序阶段事实上通常提前完成,但是经常得在第一周内。
  • 第一周至第四周大多数时间持续开发新特性。
  • 第五周完成半数的新特性开发和Bug修复。因为我们想要确保复查人员拥有至少一周时间来复查代码,在第五周末的时候代码是被认为是“特性冻结”的。通常而言,留给代码复查的时间是灵活的,并取决于特性的综合复杂度。
  • 在第六周,工程师集中精力于Bug修复、代码复查和完整性测试。本周的主要目的是在本周末尾的时候打磨并准备产品的公测。

由于在版本v(n) 的第七周应当没有安排什么事(至少从开发的视角来看,本周不止是公测周),本周也是v(n+1) 版本的第一周。如果在公测阶段没有大的事故发生,那么公测频道发布的构建将会在本周末提升为生产。万一在公测阶段有大的bug出现,那么将由引进这个bug的成员负责修复并在本周末处理接下来的构建和发布流程。

发布日是这样的……

另外有趣的一点是Capitaine Train android应用问题发布在一周的相同一天:周二。更精确一点的话,是周二早晨。和一周的其它时间相比,周二有几个优势。这样发布的大多数原因是对于大多数软件项目是通用的,但有一些别的是显示十分个性:

众所周知,周二早晨通常被认为是发布新东西的最佳时刻。这里面大部分的原因是周二是一周中最忙碌的一天。周二发布应用是帮助你们的市场开拓团队最好的方式,并且能够确保对用户影响最大化。

周二是一周中第二天(这不会令人感到惊讶是吧?呃,它其实依赖于你认为周首日的方式。在我看来,一周中的第二天是周一。)。万一公开发布转变成了bug和崩溃的戏剧表演,开发/支持团队则在周末之前有至少3天半的时间来修复bug和崩溃问题。毫无疑问,工作日要比非工作日具有更清晰的意识。

通常,周二对于Android团队而言是“镇静”的一天。我的同事Mathieu和我都住在里昂(跟巴黎500公里),一周里面,在巴黎的办公室工作3天,而在家工作2天。因为我们通常在周四和周五远程工作,所以我们倾向于将会议和讨论累积到周一以清醒我们的意识并为之后顺利地发布做准备。

公测频道,崩溃报告和舞台首演在同一条船上

我先前提到过Capitaine Train Android发布生命周期的公测阶段。在Capitaine Train,公测是通过Google Play Store的公测频道完成的。公测是私密的,人们可以请求加入。将牵涉到添加新的公测人员到我们的公测池时,我们是非常挑剔的。的确,公测人员必须既极度活跃(用户至少每周一次火车旅行)又值得依赖(我们不想让他与别人交流有关即将发布的特性的事)。公测人员有整整一周时间来测试和报告错误。

由于Google Play Store上面的崩溃报告要求用户同意发送崩溃信息,我们添加了额外的崩溃报告器:Crashlytics。当用户不想报告崩溃时,这将极端地重要。例如,对于当前的产品版本,Google Play Store和Crashlytics错误报告上的不同有有多达25倍的差异。Crashlytics帮助我们接到重要的FC(Fatal Crash)。我们也能够通过发生的次数和设备类型对崩溃进行优先级的确定。

Google Play Store提供了一个称作“舞台首展”的很好的功能。舞台首展使得你的应用的新版本只对你整个用户基础上的一个子集可见。例如,它允许你发布只对10%用户的新的构建。这对于测试新特性或者潜在在缩减服务器负荷有显著的好处。在第一个Android版本的Capitaine Train期间,我们一直跟这个特性相处。因为我们对自己的发布生命周期十分自信,我们现在很少使用它。因此,我们95%的发布是通过单次全部发布完成的。

总结

我想我已经详细解释了Capitaine Train Android团队是如何运转的。我尝试尽可能准确的描述,但可能难免忘记一些重要的点。要毫不犹豫地留下评论,我将尽量回答有关遗失信息的问题。再一次提醒,不要忘记这篇文章描述了完美应用于Capitaine Train Android团队的方法学。总是要记住,你我所有的环境条件都是不同的。在改变和适配你在自己项目上的工作方式之前,请考虑一下子你工作的环境条件。

时间: 2024-10-10 07:50:46

[翻译]Android开发方法学的相关文章

[翻译]Android 5.0之应用中实现材料设计—Material Design

上午的时候在刷Google+,看到了Abraham Williams转发了一篇强文,是Android Developers网站新发的一篇博客—Implementing Material Design in your Android App.觉得很前卫,对于新发布的Android版本号Android 5.0是一个很好的学习和了解的机会,所以就花了些时间把它翻译了下来,希望对自己.对其它人有所启发. 因为翻译Android开发博客和API也只是业余爱好,水平有限,其中不免有不准确的地方,所以把原文地

翻译Android USB HOST API

翻译Android USB HOST API 源码地址:http://developer.android.com/guide/topics/connectivity/usb/host.html 译者注:翻译的好不好不是太重要,重点是在翻译的过程中会把每句话都看认真看一遍,或者说是抱着翻译的思想来完成一个读懂的目的. USB Host通信 当你的可供电Android设备处理USB host模式时,它担任着为USB总线供电,枚举连接的USB从设备等等一个主设备应用的工作.Android 3.1及以后

[翻译]Android高效开发环境(Genymotion,Gradle,Andriod Studio)

临近十一,项目接近上线,终于有些碎片时间可以查看一些博客. 这篇博客是Android开发大牛Cyril Mottier在去年写的博客,我把它翻译一下共享给国内志同道合的朋友,同时也是对自己一个很好的锻炼机会. 原博客的地址是:http://cyrilmottier.com/2013/06/27/a-productive-android-development-environment/,在国内是可以打得开.看得到的. 原文的翻译,全文如下: 在过去的6个月,我的Android开发环境改变了很多.如

(翻译) Android ListView 性能优化指南

本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住将其翻译.本文采用了意译的翻译方式,尽可能的保持原文中要表达的内容.但是,任有几处翻译存在一些异议.请读者原谅.如果你对文章的内容有兴趣,请移步到我的blog,地址如下: 地址: http://kohoh1992.github.io/PerformanceTipsForAndroidListView

(翻译) Android Accounts Api使用指南

本文翻译自Udinic的文章Write your own Android Authenticator,可能需要翻墙才能阅读.这是译者目前能找到的介绍如何使用Android的Accounts Api最好的文章了.文章细致的介绍了如何使用Accounts Api实现账户身份验证,以及使用中需要注意的地方.如果你对此有兴趣,请移步到我的blog,地址如下: 地址:http://kohoh1992.github.io/AndroidAccountsGuide/ 哦,对了.忘记补充了,这里的文章全部都是我

【Android官方文档】翻译Android官方文档-Activities(一)

Activity是可以给用户提供交互操作的程序组件,例如打电话,拍照,发送邮件,抑或者是显示地图.通常窗口会填满屏幕,但是也可以做到比屏幕小或者是悬浮在窗口顶部. App通常由多个Activities组成,它们之间支持相互跳转.一般情况下,每个Activity在应用中都是特别的,就好像 主Activity一样,主activity是应用第一个Activity,其他Activity可以通过其他操作启动.一个新的Activity的启动,那么旧Activity就会被停止,但是系统会保存这些activit

翻译Android API Guides: App Manifest

原文在这里:http://developer.android.com/guide/topics/manifest/manifest-intro.html *Manifest译作"清单",这里沿用英文便于理解,其它术语同理. **文中链接都会跳转到android开发者网站. App Manifest 每一个应用都必须在它的根目录有一份AndroidManifest.xml文件(必须使用这个名字).Android系统必须在运行应用的任何代码之前了解一些重要信息,这些信息就来自于这份mani

Android官方训练课程翻译 Android Testing

Best Practices for Testing Android测试最佳实践 Testing your app is an integral part of the app development process. Testing allows you to verify the correctness, functional behavior, and usability of your app before it is released publicly. 测试你的应用是开发过程中必不可

[翻译] Android是怎样绘制视图的

原文:How Android Draws Views 当一个Activity获取到焦点的时候,它的布局就开始被绘制. 绘制的过程由Android framework处理.但布局层级的根节点必须由Activity提供. 视图的绘制由布局的根节点开始,通过遍历布局树和渲染每个和无效区域交叉的视图,整个布局和它的子布局(layout tree)都会被测量并绘制.反过来,ViewGroup的职责是请求它的每个子元素被绘制(通过draw()方法),而每个View的职责则是绘制它们自己本身.(意思是View