【Android源码 栏目提醒】:本文主要为网学会员提供“[word]Android代码优化 - 开发文档”,希望对需要[word]Android代码优化 - 开发文档网友有所帮助,学习一下!
Android代码优化 2010-08-20 23:40 为性能设计 1避免创建对象 对象的创建从来不是免费的。
虽然GC使得内存申请代价不再高昂但是申请总是比不申请来得昂贵。
如果你在一个用户接口循环中申请对象你将会强行执行周期性的GC在用户体验上出现一些小的―打嗝‖因此除非不得已你应该避免创建对象实例下面是一些例子可以帮助理解 当你在一组输入数据中抽取字符串时尝试返回源数据的子串而非创建一个副本。
你将会创建一个新的String对象但是它会和数据共享字符数组char。
如果你有一个返回String的方法而且你知道它的结果将会一直被追加到StringBuffer改变你的签名和实现使这个函数里面直接追加避免创建临时对象。
一个更激进的主意是将多维数组切成与之平行的一维数组 一个int数组比Integer数组要好但也有一个公认的事实就是两个平行的int数组要比一个intint对象数组要高效很多。
对于其它原始数据类型亦如是。
如果你需要实现一个存储一组对象FooBar的容器请记住两个平等的Foo和Bar数组通常元比一个定制对象数组要好当然对于此有个例外就是当你设计一个API供其它代码访问时在那样的情况下通常最好是为保证API的正确性而牺牲一点速度。
但是在你的内部代码你应该尽可能保持高效。
通常来说避免创建临时对象如果你可以的话。
更少的对象创建意味着更小频率的GC这对用户体验有直接的影响。
2用Native方法 当处理字符串时要毫不犹豫地使用诸如String.indexOf、String.lastIndexOf之类的专门方法这些是典型的用C/C代码实现的方法它们可以轻易地比实现同样功能的Java循环快10-100倍。
对此建议的一反面是调用一个native方法要比调用一个解析的方法不要将native方法用于琐碎的计算如果可以避免的话。
优先使用Virtual而非Interface 假如你有一个HashMap对象你可以声明它为一个HashMap或一个通用的Map: Map myMap1 new HashMap HashMap myMap2 new HashMap 哪一个更好 一般的会说你该选择Map因为它允许你改变其实现对于通常的编程来说这是对的但是对于嵌入式系统来说这并不是太妙。
通过接口的引用来调用一个方法要比通过一个具体类型的引用调用virtual方法花多2倍的时间。
如果你已经选择了一个HashMap因为它正好适用你正在做的事情那通过Map来调用就没有什么价值了。
考虑到IDE可以为你重构代码用Map来调用就没有太大价值了即使你不知道你代码将去向何方但是再一次的公共的API是又是一个例外好的API较少考虑性能。
3优先选择static而非virtual 如果你不必访问一个对象的字段使你的方法成为static方法。
它可以被更快地调用因为它不需要一个虚拟方法表间接调用。
同时它也是一个好的做法因为从方法的签名可以看出调用这个方法不会改变对象的状态。
4避免内部的Getter/Setter 在一些像C的语言中通常的做法是用getter如igetCount代替直接地访问字段imCount在C这是一个很好的习惯因为编译器通常能够内联这个访问并且你需要限制或debug字段访问你可以在任何时候增加代码。
在
Android这是一个坏主意。
虚拟方法调用代价是昂贵的实例字段查找代价更高。
沿用面一般向对象编程实践在公开接口中提供gettter和setter是合理的但在一个类中你应该直接访问字段。
Cache字段查找 访问对象字段要比访问本地变量慢得多如下面这段 for int i 0 i lt this.mCount i dumpItemthis.mItemsi 应该写成这样 int count this.mCount Item items this.mItems for int i 0 i lt count i dumpItemsitemsi 我们用一个显式的‖this‖来表明这是一个成员变量。
有一个相似的指引就是不要在for语句中的第二个从句中调用方法。
例如下面这段代码将会在每次迭代中都会执行一次getCount这是一个巨大的浪费你可以将它的值cache为一个int。
for int i 0 i lt this.getCount i dumpItemsthis.getItemi 通常如果你将要访问一个实例字段多次一个好的习惯就是创建一个临时变量。
例如 protected void drawHorizontalScrollBarCanvas canvas int width int height if isHorizontalScrollBarEnabled int size mScrollBar.getSizefalse if size lt 0 size mScrollBarSize mScrollBar.setBounds0 height – size width height mScrollBar.setParams computeHorizontalScrollRange computeHorizontalScrollOffset computeHorizontalScrollExtent false mScrollBar.drawcanvas 这是对成员字段mScrollBar的四次分开查找通过将mScrollBar缓存到本地变量四次成员字段查找变成四次本地变量引用这样更为高效。
同样地方法参数作为本地变量拥有相同的性能特征。
声明常量为final 考虑在类开头的如下声明 static int intVal 42 static String strVal ―Hello world‖ 编译器产生一个叫ltclinitgt的类初始化器方法它在类首次使用时被执行。
这个方法将42存到intVal并为intVal从类文件字符串常量表中抽出一个引用当这些值在后面被引用到时它们以字段查找的方式被访问。
我们可以用final关键字改进之 static final int intVal 42 static final String strVal ―Hello world‖ 这个类不再需要一个ltclinitgt方法因为常量存到直接由VM处理的类文件静态字段初始化器代码访问intVal将会直接使用integer值42而对intVal的访问会用一个相对廉价的―字符串常量‖指令来代替一个字段查找。
声明一个方法或类为final并不能直接获得性能上的好处但它确实能起到某些优化作用。
例如假如编译器知道一个‖getter‖不能被一个子类重写它能够内联这个方法调用。
你也可以将本地变量声明为final然而这并无真正意义上的性能提升。
对于要地变量只有在使代码更清晰或你不得不如为了在匿名内部类中使用时使用final。
小心使用增强的For循环语句 增强的For语句可以用于实现了Iterable接口的Collection对于这些对象一个iterator被申请用来进行接口调用hasNext和next。
对于ArrayList你最好直接遍历它但对于其它collections增强的For循环语句将会等同于显式的迭代用法。
尽管如此下面的代码展示了增强的For语句的可为人接受的用法 public class Foo int mSplat static Foo mArray new Foo27 public static void zero int sum 0 for int i 0 i lt mArray.length i sum mArrayi.mSplat public static void one int sum 0 Foo localArray mArray int len localArray.length for int i 0 i lt len i sum localArrayi.mSplat public static void two int sum 0 for Foo a: mArray sum a.mSplat zero在循环中每次迭代获取静态字段两次计算数组长度一次。
one将所有东西存到本地变量避免查找。
two用到了增强的For循环语句由编译器产生的代码拷贝数组引用和数组长度到本地变量使之成为一个遍历数组元素的一个很好的选择。
它确实在主循环中产生了一个额外的本地载入/存储使得它比起one有点慢并且长了bytes。
总之增强的For语句对于数组表现良好但对iterable对象要小心使用因为有额外的对象创建。
避免Enum类型 Enum非常方便但不幸的是当考虑到时间和速度时就让人痛苦。
例如这个 public class Foo public enum Shrubbery GROUND CRAWLING HANGING 将编译成一个900byte的.class文件在首次使用是时类初始化器在代表每个被枚举的值对象上激活ltinitgt方法。
每个对象都有其静态字段并且整个集合就存储在一个数组一个称为―values‖的静态字段上对于仅仅的三个integer来说那是太多的代码和数据了。
这个 Shrubbery shrub Shrubbery.GROUND 导致了一次静态字段查找。
如果―GROUND‖是一个static final int编译器将会将它看作是一个常量并内联它。
相反地当然是运用enum你可以得到一个更优雅的API和一些编译时的值检查。
因此通常折衷的办法是为API你应该千方百计地使用enum但是当考虑到性能时尝试避免使用它。
利用内部类使用包作用方域 考虑下面的类定义 public class Foo private int mValue public void run Inner in new Inner mValue 27 in.stuff private void doStuffint value System.out.println―Value is ‖ value private class Inner void stuff Foo.this.doStuffFoo.this.mValue 在这里我们要特别指出的是这里定义了一个内部类FooInner它可以直接访问外部类的私有方法和私有实例字段这是合法的代码的执行的结果是如预期般的―Value is 27‖。
问题在于FooInner是一个完全独立的类这使得直接访问其私有方法是非法的为了架起桥梁编译器会产生如下两个虚拟方法 /package/ static int Foo.access100Foo foo return foo.mValue /package/ static void Foo.access200Foo foo int value foo.doStuffvalue 当内部类代码需要访问外部类的mValue变量或激活doStuff方法时就会调用这些方法。
这就意味着上面的代码清楚表明了你是通过访问器来访问成员字段的而非直接访问。
前面我们讨论过访问器是比直接访问是要慢的所以这是一个由于某种特定语言方言所导致的隐性性能打击。
我们可以通过声明由内部类访问的字段和方法为具有包作用域而非私有作用域来解决这个问题。
这样运行得更快并且移除了额外产生的方法不幸的是这也意味着这些字段可以被同包下的其它类所访问这个是违反了使所有的字段成为私有的标准OO做法的再一次的如果你是在设计一个公共的API的话你可能要慎重地考虑这一优化策略。
避免使用Float类型 在Pentium CPU发布之前对于游戏作者来说做很多整型计算是很正常的事。
有了Pentium之后浮点计算联合处理器成了内置功能你的游戏通过交错整型和浮点操作比只有整型计算时运行起来要快得多。
在桌面系统上通常的可以自由的使用浮点数。
不幸的是嵌入式处理器很少具有硬件浮点支持所以所有的‖float‖和‖double‖操作都是在软件上进行。
某些基本的浮点操作可能会花费数微秒。
还有甚至对于整型数一些芯片支持硬件乘法但缺少硬件除法在这种情况下整型除法和取模运算是在软件上执行的——如果你是在设计一个哈希表或做很多数学运算这就是你需要考虑的事情。
为响应灵敏性设计 代码可能通过各种性能测试但是当用户使用时还是会需要漫长的等待这些就是那种响应不够灵敏的应用——它们反应迟钝挂起或冻住周期很长或者要花很长时间来处理输入。
在
Android上系统通过向用户显示一个称为应用无响应ANR:Application Not Responding的对话框来防止在一段时间内响应不够快。
用户可以选择让应用继续但是用户并不会想要每次都来处理这个对话框。
因此应把你的应用设计得响应灵敏使得系统不必显示ANR给用户。
通常地当不能响应用户输入时系统显示一个ANR。
例如如果一个应用在IO操作经常是网络访问上阻塞了那么主应用线程就会无法处理正在进行的用户输入事件。
经过一段时间系统认为应用已经挂起向用户显示一个ANR让用户可以选择关闭。
相同地如果你的应用花太多的时间在构建详细的内存结构上又或者在计算游戏的下一个动作上系统会认为你的应用已经挂起。
用上面的技术来保证这些计算是高效的一直都是很重要的但是即使是最高效的代码运行也是需要花费时间的。
在这两种情况下解决的办法通常就是创建一个子线程在这个子线程上做你的大部分工作。
这样让你的主线程驱动用户接口事件循环保持运行并让你的代码免于被系统认为已经冻住。
因为这样的线程化通常都是在类级别上来完成的所以你可以认为响应性能问题是一个类问题与上面描述的方法级别的性能问题。
这个文档讨论了
Android系统是如何决定一个应用没有响应的并提供了指引来保障你的应用是响应灵敏的。
1是什么引发了ANR 在
Android系统上应用的响应灵敏性由Activity Manager和Window Manager system services所监控当它监测到如下的其中一个条件时
Android就会为特定的应用显示一个ANR 5秒内对输入事件无响应。
一个BroadCastReceiver在10秒内没有执行完毕。
怎样避免ANR 考虑到上面对ANR的定义让我们来研究一下这是为什么会发生以及怎样最好的组织你的应用以避免ANR。
Android应用正常是运行在一个单独的如main线程中的这就意味着在你应用主线程中正在做的需要花很长时间来完成的事情都能够激活ANR对话框。
因为你的应用并没有给自己一个机会来处理输入事件或Intent广播。
因此任何运行在主线程中的方法应该做尽可能少的事情。
特别地Activitiy在关键生命周期方法中如onCreate和onResume应当做尽可能少的设置。
潜在地的耗时长的操作如网络或数据库操作或高耗费数学计算如改变位图大小应该在子线程里面完成或以数据库操作为例可以通过异步请求。
尽管如此这并不是说当等待子线程完成的过程中你的主线程必须被阻塞——你不必调用Thread.wait或Thread.sleep恰恰相反你的主线程应该为子线程提供一个Handler以便子线程完成时可以提交回给主线程。
以这种方式来设计你的应用将会允许你的主线程一直可以响应输入以避免由5秒钟的输入事件超时导致的ANR对话。
这些做法同样应该被其它任何显示UI的线程所效仿因为它们属于同样类型的超时。
IntentReciever执行时间的特定限制限制了它们应该做什么在后台执行的一些琐碎的工作如保存设置或注册通知。
至于其它在主线程里被调用的方法在BroadcastReceiver中应用应该避免潜在的长耗时操作或计算而是应该用子线程来完成密集任务因为BroadcastReceiver的生命周期是短暂的。
对Intent broadcast作出响应你的应用应该启动一个Service来执行长耗时的动作。
同样你也应该避免从Intent Receiver中启动Activity因为它会产生一个新的屏偷走任何用户正在运行的应用的焦点。
对Intent broadcast作出的响应假如你的应用需要向用户显示什么东西应该用Notification Manager来完成。
增强响应灵敏性 通常在一个应用中100到200微秒是一个让用户感觉到阻滞的阈值因此这里有些小技巧让你用来使你的应用看起来响应更灵敏。
如果你的应用正在后台对用户输入作出响应显示正在进行的进度ProgressBar和ProgressDialog对此很有用。
特别是对于游戏在子线程中做移动的计算。
如果你的应用有一个耗时的初始化过程考虑用闪屏或尽可能快地渲染主界面并异步地填充信息。
在这两种情况下你都应该表明进度正在进行以免用户觉得你的应用被冻住了。
为无缝设计 即使你的应用是快速且响应灵敏的一些设计仍然能句对用户造成问题——因为与其它应用未计划的交互或者对话意外的数据丢失无意识的阻塞等等。
为了避免这些问题有助于理解你的应用运行的环境和可以影响你的应用的系统交互。
总之你应该倔尽全力地开发一个与系统和其它应用无缝交互的应用。
一个常见的无缝问题就是一个应用的后台进程如service或broadcast receiver对某事件作出响应而弹出对话框这看起来仿佛并无大碍特别是当你在模拟器上单独地构建和测试你的应用时。
然而当你的应用在真正的设备上运行后台线程显示对话框时你的应用当时可能没有获得用户焦点。
这就会出现你的应用会在活动的应用后面显示对话框或者从当前应用中获得焦点并显示对话框的情况而管论当时用户正在做什么如正在打电话等。
那样的行为可能对你的应用或用户不起作用。
为了避免这些问题你的应用应该利用适当的系统资源——Notification类来通知用户。
利用通知你的应用可以通过在状态条上显示一个图标来通知用户事件已经发生而非获得焦点和打断用户。
另外一个无缝问题的例子就是Activity由于未能正确实现onPause及其它生命周期方法而无意中丢失了状态或用户数据。
又或者你的应用要暴露供其它应用使用的数据你应该通过ContentProvider来实现而非通过一个全世界都可读的原始文件或数据库。
这些例子的共同特点就是它们都是关于如何跟系统和其它应用协作得更好
Android系统的设计就是将所有的应用看作是一个松散耦合的组件联邦不是一堆墨盒代码。
这就使你作为一个开发者可以将整个系统视为只是一个更大一点的组件联邦。
这样有得于你将应用与其它应用清晰和无缝的集成所以作为回报你应该更好的设计你的代码。
这个文档讨论了常见的无缝集成问题和怎样去避免它们。
它包含了如下主题 别丢弃数据 一定要记住
Android是一个移动平台。
看起来很显然但是记住这个事实很重要就是任何Activity如‖正在打进来的电话‖应用在任何时候都有可能弹出来覆盖你的Activity这会激活onSaveInstanceState和onPause方法并导致你的应用被杀死。
如果当其它Activity出现时用户正好在你的应用上编辑数据你的应用被杀死的同时那些数据也很可能会丢失。
当然了除非你先保存了进行中的工作。
―
Android方式‖是这么做的能接收和编辑用户输入的应用需要重写onSaveInstanceState方法并以恰当的方式保存它们的状态当用户重新访问应用时就能重新获得数据。
一个运用这个行为经典的例子就是邮件应用当用户正在撰写邮件时另一人Activity开始了应用应该将正在编辑的邮件保存为草稿。
不要暴露原始数据 如果你不想穿着内衣在大街上散步同样你的数据也不应如此。
尽管可能暴露某些应用可以方便其它应用读取但这通常不是最好的主意。
暴露原始数据要求其它的应用能够理解你的数据格式如果你改变了格式你将会破坏其它没有同时更新的应用。
―
Android方式‖就是创建一个ContentProvider通过一个清晰的、深思熟虑的、可维护的API来暴露你的数据给其它应用。
使用ContentProvider就像一个Java接口来分离和组件化两段紧密耦合的代码这就意味着你能够修改你数据的内部格式而不用修改由ContentProvider暴露的接口这样就不会影响其它应用。
别打断用户 如果能确定一个用户是带有目的性的运行一个应用才是安全的。
那就是为什么你除非是直接响应当前活动的用户输入不然就要避免产生Activity的原因。
那就是说不要从后台运行的BroadcastReceiver和Service中调用startActivity。
如果这样做将会打断任何正在运行的应用并使用户恼怒。
甚至你的Activity可能成为一个―击键强盗‖接收一些用户正在为上一个Activity提供的输入视乎你的应用所做的这是这可能是个坏消息。
取代直接从后台直接产生Activity UIs你应该用NotificationManager来设置通知这将会出现在状态条上当用户空闲时可以点击它们来看你的应用向他们显示了什么。
注意当你的Activity已经在前台时所有这些都没适用这时对于输入的响应用户期望看到你的下一个Activity。
有太多事要做在一个线程里做 如果你的应用需要做一个代价高昂或长耗时的计算你可能要将它移到一个线程里。
这个将会防止显示―Application Not Responding‖对话框给用户最终导致你的应用完全终止。
默认地在一个Activity中的代码和其所有的View运行在同一个线程上。
这与处理UI事件的线程是同一个。
例如当一个键被按下时一个key-down事件被添加到Activity主线程的队列。
事件处理系统需要很快地让这个事件出列并处理这个事件。
不然系统数秒后将会认为应用已经挂起并替用户杀死这个应用。
如果你有长耗时的代码让它在你的Activity上内联运行将会在使它运行在事件处理线程上这很大程度上阻塞了了事件处理句柄。
这会延缓输入处理并导致ANR对话框。
为了避免之将你的计算移到一个线程中。
在为响应灵敏性设计中已经讨论了如何做。
5不要过载一个单一的Activity屏 任何值.
上一篇:
手机安卓Android系统概述
下一篇:
试析影响公路路面平整度的因素及应采取的施工措施