资深谷歌安卓工程师对安卓应用开发的建议

擅长Java语言的资深开发者们,多年以来可能是工做在网页,服务器,和桌面系统等开发领域。这些领域的经验帮助他们创建起来了本身使用Java语言的模式和本身的Java库的生态系统。可是移动应用的开发却和这些领域的java开发有着天壤之别。优秀的安卓应用开发者须要考虑到移动设备的限制,从新学习怎么样去使用java语言,怎么样去有效地使用实时环境和安卓平台,而后写出更好的安卓应用程序。javascript


About the Speaker: Romain Guy

Romain是谷歌的安卓工程师。在加入Robotics以前,他在安卓Framwork组参与了安卓1.0到5.0的开发工做。他如今又从新加入了安卓的新UI和图形图像相关的项目。java

About the Speaker: Chet Haase

Chet 也是谷歌的工程师。 他如今是安卓UI Toolkit 组的组长,他擅长于动画,图像, UI控件和其余能带来安卓更好的用户体验的UI组件的开发。他还擅长撰写和表演喜剧。linux


介绍 (0:00)

本次演讲是以安卓平台组写的近10篇文章为基础的。全部的文章都可以在Medium网站上看到,文章的第一部分请看这里. 今天咱们会讲到这些文章里面的一些东西,若是你对特定的话题感兴趣或者想深刻了解它们,请去阅读原文。android

为何移动开发如此艰难? (1:47)

有限的内存 (1:47)

咱们发现谷歌公司里面的应用开发者有一个大问题,他们对口袋里天天都携带着的安卓手机的本质都有些误解。这些设备内存,CPU的处理能力和电池的待机能力都很是有限。开发者们必须理解大家的应用不是在设备上惟一运行的应用。内存是很是有限的资源,而且被整个系统共享着。我所在的Android平台组很是当心地对待这个问题,这也是为何有的时候咱们建议的一些规则看起来会有一点极端的缘由。咱们须要有全局的眼光来看待这个问题,由于系统会同时运行20、30、40个的进程。当你只为单一应用开发的时候,牢记这些限制是比较困难的。算法

今天咱们手上的设备每每会有1-3GB的内存,但不是全部的安卓设备都有这么多的内存的。安卓阵营中有14亿的设备,其中许可能是两三年前的产品。事实上,他们才是着14亿设备的主力军。大部分的设备不是在美国和西欧,而是在中国和印度这样的国家。在这些国家里,安卓设备必须便宜才能吸引更多的消费者。shell

安卓在从4.3到4.4的演进过程当中,咱们使出浑身解数才使安卓系统可以成功地在内存受限的系统上运行起来。很长时间以来,由于人们想在便宜的500MB内存的设备上使用安卓系统,姜饼系统成了他们惟一的选择。固然,如今的状况已经大不相同,姜饼设备已经差很少消失殆尽了。可是,由于如今的安卓版本有着庞大的大内存消耗的应用群,这就须要更强大的框架和平台。数据库

这是一件你必须持续思考的事情,可是这很难,由于它不总在你优先考虑的事情的范畴内。你可能知道Knuth的名言“过早的优化是一些罪恶的根源”。虽然我赞成他的观点,但是当你写完你的应用,而后打算从应用中再减小50MB的内存的使用,也是一件很是困难的事情。咱们不是说你须要毁掉你的应用系统架构或者牺牲你的测试,可是每一次你能作些改进来改善你的内存使用状况的时候,请立刻动手。编程

为了整个设备有更好的总体用户体验而开发。当你的应用很大的时候,你会致使系统杀掉别的应用,这样你就影响到了别人应用的用户体验。当别人的应用不注意内存使用状况的时候,你的应用体验也不会好。大家须要互相交流而后找到和谐共存的方法,请牢记这条建议。缓存

若是系统须要杀掉除了你的应用以外的全部应用,那么当你的应用退出之后,其余应用须要从新创建他们可运行的环境。这样你的应用会看起来像一个恶意的应用。若是你想要让用户重入你的应用时有一个良好的体验的话,请尽你所能将你的应用保持在后台运行而且消耗最小的内存。安全

在有些应用当中,实现内存回调接口是很是管用的。你能在API文档中很是容易的找到onTrim的内存回调函数,而且能实现多个级别的Trim操做。基于你的Trim操做的级别,你能够释放一些缓存和bitmaps,也能够退出一些Activity,或者其余的任何能帮助你留在后台运行的措施。

CPU 处理器 (8:07)

移动处理器显然比桌面和服务器的处理器能力要慢不少。虽然从外界不容易察觉,可是移动处理器大部分时候都是处在过热降频保护的状态。这意味着尽管CPU的频率很高,可是仍然不能像桌面和服务器的处理器同样快。当诸如用户在屏幕上拖拽的时候,CPU能够达到它的最快处理速度。但是在其余的状况下,CPU处理器是跑在待机频率上的,否则的话,电池不能保证用户的基本待机时间。

若是你买的是2GHz的台式机或者笔记本,CPU大部分时候是能跑在那个参数指标上的。但是你若是买的是一个2GHz的手机,CPU只能有时候跑在2GHz上。若是你买的是八核的手机,硬件上你是有八个处理核,可是他们大部分时候不会一块儿工做。基本上,手机盒子上的参数表和你实际上能达到的参数是有不一致的地方的。这主要是为了降频保护CPU,提升电池待机时间和减小发热量。

GPU 图像处理器 (10:10)

和CPU同样,GPU也有保护降频的功能。纹理加载(Texture uploads)的开销特别大。全部bitmap的操做或者结果是bitmap的操做都会被加载到GPU。举个例子说,当你在绘制路径时,这些东西会转成bitmap而且做为纹理加载到GPU,这时系统的开销就会特别大。你的性能瓶颈就可能在这。

填充率和分辨率之间也是相关联的. Nexus 6P的分辨率很是高,换句话说,屏幕上有许多的像素点要填充。可是GPU的性能带宽却跟不上。系统为了填充全部的像素点而超负荷的工做。你的要求越高,系统须要的时间就越长。

一个提高UI 性能的技巧就是避免过分刷屏。你须要系统填充屏幕上每次像素的次数越多,当屏幕愈来愈大和分辨率愈来愈高的时候,状况就会愈来愈糟。若是你有一个Playstation 4或者Xbox One的话,他们在运行那些1080p的游戏(每秒30帧)时十分卡顿。在咱们的手机上,咱们有更高的分辨率,并且咱们以每秒60帧的速度完成全部的事情;咱们尽量的完成更多的事情而且消耗更少的电量。这就是为何咱们对图片的处理性能有着诸多的要求以及对应用优化的建议,由于咱们没有你想象的那么强的处理能力和电池电量。

内存 = 性能 (12:29)

若是你有大的应用,那么就会发生更多的内存页的置换,更慢的内存分配。实际运行中,系统须要遍从来决定新的对象能够放到内存何处,这须要花费更多的时间。回收也会须要花费更多的时间;每次须要内存的时候都要遍历更多的东西。

总体上内存回收机制也被更多地触发。在咱们的演讲中,咱们详细阐述了内存回收是如何工做的,并且讨论了ART和Dalvik的改进。详情请看 上面的视频 at 13:06

低端设备 (19:40)

你口袋里面的设备保证比你用户的设备要快不少。你认为的那些老旧设备并无消失。 A 它们依旧在用户的口袋中,并且用户打算尽量长地使用它们。B 那些老旧的设备虽然慢可是便宜。这意味着它们能吸引哪些不可以购买最快设备的人们。

流畅的帧频率 (21:01)

找到一个流畅的帧频率是十分困难的。你只有16毫秒的时间去完成全部的事情。这些事情包括触屏事件的处理,计算,布局,绘制帧,而后交换缓存。16毫秒意味着每秒刷新60帧,这是咱们在安卓系统上要求的,由于咱们是V-Sync。咱们不但愿屏幕花屏。花屏在有些游戏中你能看到,由于它们在一秒内同时有两个buffer。那个看起来太可怕了,咱们不想让你这么作。这就意味着若是你仅仅多花了一点时间,哪怕17毫秒,咱们就会跳过一帧。而后跳过一次V-sync,这样系统就不是60fps了,而是30fps。咱们叫这种现象为Jank。

系统在60fps和30fps中来回跳跃是件很是糟糕的事情。这会让你的用户以为你的应用很是janky和不一致。也有可能你的应用会一直表现糟糕,一直都是30fps。这就是许多游戏当它们认为作不到60fps的时候,它们就一直都是30fps。

实时运行: 语言 ≠ 平台 (22:48)

几周前,有人问我 “当有新版本的JDK发布的时候,你通常作什么?”。 而后我意识到他们并不理解语言和实时运行环境是不同的。当有新的JDK发布的时候,我基本不关心。它和安卓运行时一点关系都没有。咱们使用同一种语言,但那不意味着咱们在运行的时候是同样的。

当人们使用Java语言的时候,有三个方面的因素构成了整个java体验。Java编程语言自己,实时运行环境(在有些server环境中叫HotSpot)而后是硬件设备。

对于那些擅长服务器的人来讲,服务器的实时运行环境提供诸如移动,压缩收集器的功能,这些都意味着临时的内存分配带来的系统开销很是小。这些事情和移动指针同样快。在服务器环境中,的确如此。而后你会理所固然的认为移动设备有同样快的处理器,同样大的内存,因此处理能力就应该和服务器同样无限制。

在安卓系统中,状况是很是不同的,特别是你习惯于无限的系统资源和彻底不一样的实时运行环境的时候。咱们有Dalvik和ART。咱们没有压缩,这意味着当你分配一个对象的时候,这个对象就会存在堆里面。堆会碎片化,也会使得寻找空余的内存空间变得开销更大和更困难。在ART环境中,咱们有空闲时的压缩。ART历来不能在应用是前台运行的时候压缩堆空间,可是当它在后台运行的时候是能够压缩堆空间的。在进程的生存周期里面,有的时候压缩是会发生的,这会帮助系统寻找空闲内存。

压缩在本地代码上的做用更为关键。若是你的应用使用JNI,而且分配一个指针给一个java对象的时候,那个指针一般会被系统认为一直有效。在ART环境中,若是你的堆被压缩了,那指针就会变成野指针。若是你使用JNI,在开始的时候你就必须对这种状况特别当心。

UI 线程 (26:56)

安卓是一个单线程的UI系统。技术上,你能够有多个UI线程,但这会带来不少麻烦。正如你担忧的那样,全部的UI控件都是在同一个线程里。由于是一个UI线程,因此你任何阻塞UI线程的操做都会带来性能上的影响,jankiness(闪屏)和不一致性。在UI线程上分配内存就更糟了。若是你有后台运行的线程分配内存,当虚拟机VM阻塞全部的线程(包括UI线程)十几毫秒的时候,你就会跳过一帧。这样,你就只有30fps,这会很是糟糕。

存储 (28:53)

存储的性能没有一个定论。有时候人们把数据存在SD卡上,这就会有各类各样的性能标准。当存储设备快要满的时候,即便在同一个存储设备上也是有不一样的性能体现的。可是若是你的应用是依赖每一个测试设备的存储速度的话,那总归是很差的。写flash存储的时候也意味着控制器须要作不少事情,由于它须要收集信息,记录哪些空间已用和哪些空间没用。换句话说,若是你的应用在后台作大量的IO操做的话,你会把这个系统拖慢。你应该有这样的经验,当Play Stoer 在后台安装应用更新的时候,你的设备会忽然间感受慢了起来。由于它在后台作磁盘写操做,因此前台的应用读它们本身资源也会变得很慢。

存储的大小也是各式各样的,因此不要让你的应用对特定的存储大小有依赖。APK的大小也很重要。你应用中资源的大小会影响到你的启动时间。

优化你的应用资源的办法有不少。如今在老的版本的安卓上,也有了矢量图。若是能够的话,请使用安卓的SVG库。虽然对于图标来讲不是太合适,但的确能对你的应用起好的做用。你也可使用WebP格式,这会比PNG省下20%-30%的存储空间。PNG Crush也是一个很好地离线工具。APT作了一部分PNG crush的工做,可是还有不少工具会作的更好,更有效。

网络 (28:53)

你使用的网络确定比大多数用户的网络要快。他们可能还在用着2G网络,而且流量很贵。因此你的应用不该该依赖持续的网络链接。也许你应该让你的应用可以智能地下载它须要的内容或者你的应用不须要依赖网络下载的内容就能使用,真正的内容能够晚点再去下载。

开车去Utah的路上,你能够测试在糟糕的网络环境下,你的应用的表现如何。或者你能够用一些模拟糟糕网络的工具。全部这些应用的测试都是手工进行的。

每一台设备都是一个村庄 (33:31)

每一台设备都是一个村庄,这意味着每一个在村庄里的人都须要共同努力来维护一个好的用户体验。你可让这个体验特别差,也能够特别好。若是你的应用想成为好的体验的支持者,你能够试着在你的manifest里面关闭Service和broadcast receivers。在代码里面,当你的应用发现用户打算使用你的应用的时候,你才动态的开启你的services和receivers。邮件客户端就是一个好的例子,当用户安装邮件客户端的时候,全部的一切都应该处于关闭的状态。一旦用户点击了该邮件客户端而且加入了帐户的时候,你才开启你的services和receivers。而后下次用户重启设备的时候,Framework就会记录下来,你的服务就真正的运行起来了。这个方法很简单,但也很重要。由于你的services虽然运行起来了,可是没有人用,你会对别人带来没必要要的坏的影响。

另一个现象叫作”公共地带的悲剧“: 每一个人都认为他本身的应用是最重要的。若是每一个人都是这样的观点,那么每一个应用都会很是大并且尽量被激活,这样设备会承受不了。勿以恶小而为之,勿以善小而不为。

技巧与建议 (35:53)

了解你使用的语言 (35:55)

开发者们可能有了不少年Java的经验,可是却不能最好地利用语言来发挥出移动设备的最大性能。

不要使用Java的序列化

序列化自己是很是有用的,这不是咱们如今讨论的话题。若是你用过序列化的话,你会发现不是太好用,由于你须要产生UUID,并且它还有限制。序列化也会慢,由于它用到了Reflection。

使用安卓的数据结构

其余场合Java开发者经常使用到的一些集合类和方法在Android上也许就不合适了。Autoboxing 和 Primitive Java 类型的替代品在Java领域里使用的很是普遍。集合的迭代器也是很是经常使用的。在Android平台上,咱们特别建立了一些集合类来替代这些模式。对于Key来讲,咱们使用基本类型,因此若是hash表里面是一个整形做为key的时候,咱们没有autoboxing。你能够以使用SparseArray类,一样的,ArrayMap和SimpleArrayMap也是HashMap的替代者。

为了不java package中基础类型的额外开销,使用Android的数据结构类型是个不错的选择。HashMap里面的每一个条目都会多用4倍的内存空间,像你的int类型同样。你放在hashmap里面的内容越多,你浪费的内存资源也就越多。看看你如今已经使用的数据结构,若是你打算在某些状况下重写你本身的数据类的话,不要犹豫。

当心使用XML和JSON

他们相对来讲太大了,对于你的某些应用来讲,他们或许太结构化了。若是你使用有线设备上的数据格式会更加简洁,可是至少你能在你作网络传输的时候gzip这些数据。固然,若是你打算序列化你的对象到磁盘上,你可能须要找找其余的替代方案了。

避免使用JNI

有些时候你须要JNI,可是若是不方便就不要使用它了。JNI有些有趣的事情:每一次你越过JAVA和Native的边界的时候,咱们都须要检查参数的有效性,这会对GC的行为有影响,并且带来额外的消耗。有的时候这些消耗是很是昂贵的,因此当你使用了不少JNI的调用的时候,你可能在JNI调用自己上花的时间比在你native代码执行的时间都要多。若是你有些老的JNI代码,请仔细检查他们。

有一件你能提升JNI效率的事情就是尽量的把屡次调用集中到一次。避免在JAVA和Native中间来回调用,一次搞定。例如,在Android的Graphic Piplline中,咱们在每次调用JNI的时候传入了尽量多的参数,从而避免了调用JNI的时候,JNI从JAVA对象中抽取参数的开销。咱们使用了基础类型,避免了使用奇怪的对象,咱们传入的是Left,bottom和right参数,因此咱们不须要又返回到JAVA层了。

基础类型 vs. 封装后的基础类型

请使用基础类型来代替封装后的基础类型。一个不那么明显的事实是:若是你使用那些集合类而且比较它们是否相等的时候,咱们每次都会作autobox。每次都须要分配一个对象去使用,由于它们会被强制转换成boxed equivalent。这里有个例外,你可使用big boolean,由于它们只有两个。

避免使用反射(Reflection)

比避免使用JNI更进一步,我须要如我建议避免使用JNI同样指出来,animation framework使用了JNI来避免使用反射。这样固然多了双重内存分配的开销,由于咱们须要分配这些对象而且每处都要autobox他们,同时从Java运行环境发起的函数调用才用的内部机制的开销特别大。

当心使用 finalizers

内部咱们只在颇有限的状况下才用。关于finalizers有个事实不太明显的是:它须要两次完整的GC才能收集完全部的事情。若是你在一个finalizer里面放置某些assert来收集信息的话,咱们须要运行两次完整的GC才能回来处理。有些时候这是必要的,可是在其余的一些状况下,把这些处理放在离finalizer以外的近点的地方会更方便。否则的话开销太大了。

网络 (47:19)

不要过分同步

正如前面描述的那样,你的用户的网络可能不那么好,或者他们的网络会很贵。并且,你会给系统带来负担。也许你认为你的应用须要尽量快地获得那些数据,可是事实上是你会给系统带来不少的负担,仅仅为了保持你的应用处于激活状态。

容许延时下载

这在信号很差的网络和收费很贵的网络下十分重要。你能够把数据打包起来。把全部须要下载的东西收集在一块儿,而后作一次同步。

谷歌云消息 Google Cloud Messaging

GCM收集了不少东西。他会使用传送层来和后台服务器传送数据,因此你也许能够重用这个系统来避免本身重复工做,也避免了每一个应用都建立本身的socket。

GCM 网络管理(Job 调度)

这也是个很好的东西能够利用起来。Job 调度可让你把你的东西打包起来。你能够说 “我想在空闲的时候才使用这些事务,或者当我被激活的,或者当我在wifi连上的时候”,而后在普通的时间间隙里面收集这些事情。这会更有效,对用户也不会太突兀。

不要轮询

永远不要轮询

只同步你须要的

你知道你的应用里面发生了什么,因此仅仅只要同步当前应用须要的数据,而不是把全部可能须要的数据都同步下来。

网络质量 (50:05)

不要对网络有任何的假设。为低端网络开发,而后保证你的应用在低端网络下测试过。即便是你在使用模拟器,你也须要保证你在咱们称为“糟糕的网络”的环境下测试过。在这些状况下,你应用的表现会让你大吃一惊的。

数据 & 协议 (51:13)

若是你拥有服务器,作全部能帮助到设备的事情。好比改变数据格式,改变发送数据的类型。设备来告诉服务器它打算浏览图片的大小,也是个好方法。咱们看到过内部的应用接收到了比屏幕大小大4倍的图片,而后在设备端从新处理图片的大小。这个工做明显服务器来作比较合适。

使用缩小和压缩的算法。gzip是个好的选择。若是你能在HTT上传送GZIP的数据,请这样作。这会颇有用,特别是在糟糕的网络上。GCM也能够帮忙。它会帮助你保持链接,因此,一个好处就是使用它会有更短的延时,由于不用在每次链接的时候都作一次握手并且在延时方面,移动网络是出了名的糟糕的。

存储 (52:21)

不要写死你的文件路径

路径会改变的,若是用户想把它们存到别处呢?

仅仅固定相对路径

你知道你的数据相对你的APK的路径,这是正确的,安卓有APIs获得那些路径。你不须要作hard code的事情,你只须要坚守和你APK实际安装位置的相对路径。

使用存储缓存来处理临时文件

有APIs能够调用,请使用它们。

简单状况下不要使用SQLite

SQlite的消耗在某种程度上也是很大的,因此若是你只须要些简单的方式,好比 key-value存储会更加合适些。

避免使用太多的数据库

数据库总体上开销是很大的。也许你能够试试只用一个数据库,而后服务于多个不一样的用户。

让用户选择内容存储位置

当用户有可移除的存储设备的时候这些尤其重要。或者他们可使用adoptable storage,这是在棉花糖版本上的新的方法。若是用户打算采用新的存储设备,而后把数据都迁移到它上面,你的应用应该容许他们这样作,这样你的应用就不会乱掉了。

问和答 (54:00)

问:你提到了一个平滑的策略是下降你的帧频率,有可能一个应用说: “我须要30fps而不是60fps吗?”

Romain:某种程度上。有一些你可使用的API来达到和屏幕同步的目的。你能够每隔一帧同步一次。在普通的应用中,这会是很困难的,除非你知道你会由于外面各类各样的设备而有许多的工做量。可是,若是你使用的是OpenGL或者Canvas,你又有本身的线程作渲染,而且你对渲染线程有完整的控制权的时候,状况就不同了。在这种状况下,你能够控制你的帧,你能够等待,而后你记录V-Sync,诸如此类的动做。若是你想深刻的了解这些高级的技术细节,你能够看看那些游戏开发者的文章,他们在作相似的事情。

Chet:通常状况下,你能够尝试60fps,而后你可使用一个叫GPU Profile的工具,这个工具在Android Studio上就有。工具里有一个彩色的显示条,你须要保证你持续的呆在绿线如下。你能够在developers.android.com 查到工具的详细信息。

Romain: 还有,咱们常常用 Systrace。Systrace是一个底层的,轻量级的,系统层面的监测工具。若是你的应用在任何地方有性能问题,它不须要嵌入在你的代码中。Systrace不但能显示你在那你花费了时间,并且还能显示你的线程是否被调度,CPU的运行频率,你是否是被从一个CPU转移到另外一个CPU上(这也会影响到你的性能),是否后台线程致使了锁定,或者优先级倒置。文档在developer.android.com上也能找到。这也是个颇有用的工具。

问:有一个类叫‘android.os.memfile’。 它和linux 共享内存联系在一块儿。当我使用它的时候,我能从linux 内核中获得300-400MB的内存来共享。因此,我使用它来分享拍摄的视频而且存到了临时区域。同时我把它设为 non-purgable. 由于使用了400MB的内存,Android总会参与进来刷新内存。你对使用这个技术来存储奇怪的内存有什么建议吗,或者不要使用共享内存?

Romain:这是个很是有趣的问题。我没有什么想法。我也不知道系统有这样的行为,也许你应该问问内核组来理解这里的行为。若是是如设计同样,那么后果是什么。对不起,我没有一个更好的答案。

问:我想指出当Android在切换Activity状态的时候,会使用共享内存。他们发现若是你一直监视着你的Android设备中共享内存的使用状态,你能够准确地猜出Activity是处于什么状态,如:是从onResume到onStop,因此这看起来像是个安全漏洞。显然,安卓再每次作Activity的状态转换的时候都使用了共享内存,有时候是12bytes。因此实时监测共享内存的状态,你就能够知道Activity是在什么状态。

Romain: 若是你一直监视着内存状态,你可能还能够知道其余的发生在设备上的事情。好比看着屏幕。可是,很高兴你能分享这点。

问:你以前讨论到应该避免使用JNI,也须要避免使用Reflection。而后你提到你在动画实现中忍受了许多开销来避免使用Reflection。我想知道若是使用了Reflection,会在动画对象中发生些什么呢?

Chet: 咱们没有用Reflection,咱们用的是JNI。JNI里面有Reflection的代码。它是这样工做的:他会调用到JNI说,“我须要一个方法”。因此,当你想使用一个动画的属性叫作“foo”。你传入了一个串,而后说“好吧,我想找到一个设置的方法叫作setFoo”。进入JNI层后,看起来JNI层会说:“有这样的方法的签名吗?”。若是JNI找不到,就会返回false而后就会使用Reflection。我认为Reflection的代码应该永远都不被调用到。因此JNI须要返回false。我之前也使用过Reflection,只要是我写的代码,我会使用已经存在的接口来保证没有其余奇怪的事情发生。而后,在使用Reflection的时候我还会有些backup的机制。可是我不认为Reflection须要这样用。这种状况下可使用JNI。在有些代码中,也许不太明显,可是若是你找到代码正确的地方,你会发现一个状况是“JNI够用吗?若是他够用,就使用JNI好了。”

Romain: 当你使用JNI的时候,你其实能够有效地访问到全部的成员和方法,这就是Reflection作的事情。咱们发现使用JNI会比Reflection快上不少。

Chet: 并且部分缘由是内存的消耗。由于至少算上运行的开销,JNI是不会额外再分配内存的。经过任何机制实现方法查找都不简单。可是,咱们只会在你第一次调用的时候作方法查找。因此,当你建立动画对象的时候,第一次你运行的时候,就开始寻找setter或者getter,而后缓存它们。这样一样的状况不会再次发生。

Romain: 另外一个事情是当你经过Reflection调用一个set alpha的函数的时候,你会在方法对象上调用该方法,这须要一个对象。因此当你须要传入一个Float时,你须要分配内存来封装(boxing)你的Float。可是若是你用的是JNI,你就不须要boxing。

Chet: 固然还有第三种机制,若是你打算习惯它的话,就是属性(properties)。这就是咱们为何很早前就加入属性(properties)。为一些特殊的视图咱们建立了属性,alpha属性,translation X属性, rotation X属性等等。他们直接使用setters。因此当你使用属性的时候,他直接调用了静态的属性对象。你在视图的实例中传递它们,它会调用视图的seter,这样是开销最小的方法。

问:你刚才说大家不太关心Java的版本发布,由于Java语言和实时运行是分开的。可是我仍是很好奇大家如何看待 retrolambda,它让你使用lambdas,这是Java 8 的功能,可是它把Lambdas编译成二进制代码。并且做为dexing的一部分,它把他们重编译成匿名类。你有什么建议吗?

Romain: 我知道retrolambda,并且我喜欢它。我以为它太棒了。使用它以前,我会反编译retrolambda的结果看看发生了什么。由于使用lambda,我敢确定你会有些讨厌的惊喜。我很是确定它们在某些地方会变成匿名类,因此在你会遇到大量的内存分配,分配状况取决于它们是如何介入的。例如,若是我在一个循环中传入一个lambda,内存分配会发生什么呢?因此,我会去看看反汇编代码,而后在我决定前看看发生了什么。你知道,匿名类能够被缓存,或者它会在每次使用的时候分配,我不知道具体会发生那种状况。我须要看看。做为一个软件工程师来讲,在决定如何使用前,尽可能的去理解背后到底发生了什么是很是重要的。由于这样作,就不会在以后的开发中遇到讨厌的惊喜。固然,在某些状况下,若是你是在用户点击一个按钮时使用了lambda,并不重要,对吗?当你点击一个按钮时,性能不是个特别大的问题。

问:我想知道的是何时大家会支持Java 8, 而后你不须要把他们编译成匿名类。

Romain: 不知道,实话实说,这几个月咱们确实讨论了很多关于桌面系统的JAVA 8的事情。我使用过streams,parallel streams和lambdas。这些都太棒了。代码看起来特别棒,写起来都特别有趣。可是当你意识到在某些状况下,他们确实会比老的循环更慢些的时候,感受就不同了。再说一遍,当心一些,尽可能去理解你如今正在作的取舍。关于Java 8语言何时会被Android支持,我不知道,由于咱们没有讨论过这个事情。并且即便我知道,我也不能告诉你。

Chet: 我有必要提一下,我最喜欢的一个工具就是DDMS里面的Allocation Tracker,它如今多是在Monitor的什么地方。反编译二进制代码是一个好的方法,这样能够看看到底编译器干了什么,可是运行的时候看看发生了什么也是个好方法。当咱们刚开始开发动画框架的时候,咱们使用Allocation Tracker去找到咱们在每一帧的时候分配了些什么,而后消除它们。因此,若是你用retralambda,你须要肯定在你应用的关键点,你不要分配那些从外部看不须要的内存。

问:咱们平常的工做都是feature驱动的,对吗?内部,咱们如何去维护一个性能的标杆?大家有专职的QA来跟踪大家的代码,而后你能够看栈的trace,而后时刻提醒你,或者做为一个feature的开发者在大家提交给QA前大家有什么标准吗?

Romain: 这是个混合的问题。对于那些很是关心他们应用的性能的工程师来讲,这是个他们开发过程当中很是当心的问题。也有QA会关心这个话题,可是这几年来,Android组写了不少自动化的测试用例来测试性能。例如,拿Butter项目来讲,图像组为每个CL作了一个jank的dashboard。他会运行一些像在Gmail中滚动滚动条的用例,而后计算咱们错过的帧。每一次数字的变更,咱们都会收到一封邮件,而后有人就会受到批评。固然应用的开发者会先收到批评,而后他们会发给咱们框架组说:“大家太烂,大家的东西太慢”。而后咱们会回复它,这样会来来回回讨论好长时间。

Chet: 咱们也会写出许多工具而且改进它们。Systrace就是一个咱们内部正在使用而且持续改进的一个很好的工具,以后咱们提供给全部人使用。而且咱们持续迭代地开发它。这样咱们就像最近作的事情同样,在一些不明显的状况下,给你一些关于你的问题的提醒和信息。

Romain: 事实上,大部分的工具你能够经过工程的UI看到不少信息。因此诸如Hierarchy Viewer, devel-draw,debug tool 和 GPU profiler都在设备上能够运行。全部的这些工具都是咱们内部须要的,因此咱们开发出来而且提供给全部人使用。固然,还有不少的事情能够作,可是关于性能最重要的事情就是大家能写出自动的测试用例来捕捉它们。这是很是困难的一件事情。咱们暴露了一些内部的计数器,我想咱们有文档描述它们。在adb shell的dumpsys帮助下,你能够获取其中的一些信息,帧的时间戳,janks的次数,特别是在棉花糖和棒棒糖中咱们加入了更多的方法。你能够作的事情就像UI automater同样,你能够建立一个用例,驱动你的应用,而后输出这些计数器看看可否告诉你些什么有用的信息。更有效的方式是有个dashboard。它抓取了这些命令的输出,而后给你画一个特别好看的图表。另外一件事情是,当你作些事情的时候想一想你的性能。提及来简单,作起来难,评判起来更难,理解你在量化什么还要困难一些。可是,这是惟一的途径。

问:你提到不要使用序列化。你能再详细的描述一下吗?在Activity和Fragment间使用Serializable和Parcelable传输数据包含在里面吗?

Romain: 咱们是在讨论Serializable的接口。Parcelable是Android提供的Serialization的一个变种,是很是有效的,可是仅仅用来进程间的数据传输。如今咱们有新的方式了,搜索“persistent parcel”,你就能够找到它了。可是对了,咱们讨论的是Serializable接口。

问:你以前提到flat buffers。有一种说法是若是网络请求须要很长时间的话,节省100多毫秒不过重要。

Romain: 我以前给的例子比较有意思是由于他用了JSON做为磁盘上文件存储格式。这里的想法是你已经缓存了数据了,因此你已经承受了网络的开销,因此能够优化的地方在你屡次读取这些数据的时候。在这种状况下,使用更快的方式是有意义的。可是你是对的,若是解析JSON须要10毫秒,解析你的flat buffer只须要5毫秒,可是网络传输须要200多毫秒,这样的优化还有做用吗?显然,我但愿你的应用在后台线程处理全部的网络操做。因此这里讨论的更多的是UI的延时,而不是别的。若是你有什么东西是常常访问的,你固然须要在flat buffer 这样的地方里面寻找它。

还有一个Cap’n Proto,也是protobuf v2的开发者开发的。他写了一些相似flat buffer的东西,可是更加深刻。由于它能够被做为有线传输格式,能够被用做RPC机制,并且我相信它有Java 封装层。这就回到咱们以前的坚持,当你考虑性能的时候,你须要量化标准。肯定你修改的是正确的问题。若是你修改的地方不是个问题可能还无所谓。做为咱们来讲,在你的应用里面到底什么会是个问题更难。由于这彻底依赖于你的应用。咱们只能分享咱们在应用中一次又一次看到的问题,再一次强调,这不意味着你的应用也有一样的问题。可是真的,若是你在磁盘上作serialization,看看其余的格式吧。总而言之,找到你最好的方式。

相关文章
相关标签/搜索