【Android源码 栏目提醒】:网学会员为广大网友收集整理了,Android代码优化 - 教育,希望对大家有所帮助!
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 HashMapHashMap 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 idumpItemthis.mItemsi应该写成这样:int count this.mCountItem items this.mItemsfor int i 0 i lt count idumpItemsitemsi(我们用一个显式的”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 42static String strVal “Hello world”编译器产生一个叫ltclinitgt的类初始化器方法,它在类首次使用时被执行。
这个方法将 42存到 intVal,并为 intVal 从类文件字符串常量表中抽出一个引用,当这些值在后面被引用到时,它们以字段查找的方式被访问。
我们可以用 final 关键字改进之:static final int intVal 42static 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有点慢并且长了4bytes。
总之,增强的 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 的话,你可能要慎重地考虑这一优化策略) 。
9)避免使用 Float 类型在 Pentium CPU 发布之前,对于游戏作者来说做很多整型计算是很正常的事。
有了 Pentium之后,浮点计算联合处理器成了内置功能,你的游戏通过交错整型和浮点操作比只有整型计算时运行起来要快得多。
在桌面系统上通常的可以自由的使用浮点数。
不幸的是,嵌入式处理器很少具有硬件浮点支持,所以所有的”float”和”double”操作都是在软件上进行。
某些基本的浮点操作可能会花费数微秒。
还有,甚至对于整型数,一些芯片支持硬件乘法但缺少硬件除法,在这种情况下,整型除法和取模运算是在软件上执行的——如果你是在设计一个哈希表或做很多数学运算这就是你需要考虑的事情。
为响应灵敏性设计代码可能通过各种性能测试,但是当用户使用时还是会需要漫长的等待,这些就是那种响应不够灵敏的应用——它们反应迟钝,挂起或冻住周期很长,或者要花很长时间来处理输入。
在
Android 上,系统通过向用户显示一个称为应用无响应(ANR:Application Not Responding)的对话框来防止在一段时间内响应不够快。
用户可以选择让应用继续,但是用户并不会想要每次都来处理这个对话框。
因此应把你的应用设计得响应灵敏,使得系统不必显示 ANR 给用户。
通常地,当不能响应用户输入时系统显示一个 ANR。
例如,如果一个应用在 IO 操作(经常是网络访问)上阻塞了,那么主应用线程就会无法处理正在进行的用户输入事件。
经过一段时间,系统认为应用已经挂起,向用户显示一个 ANR,让用户可以选择关闭。
相同地,如果你的应用花太多的时间在构建详细的内存结构上,又或者在计算游戏的下一个动作上,系统会认为你的应用已经挂起。
用上面的技术来保证这些计算是高效的一直都是很重要的,但是即使是最高效的代码运行也是需要花费时间的。
在这两种情况下, 解决的办法通常就是创建一个子线程,在这个子线程上做你的大部分工作。
这样让你的主线程(驱动用户接口事件循环)保持运行,并让你的代码免于被系统认为已经冻住。
因为这样的线程化通常都是在类级别上来完成的,所以你可以认为响应性能问题是一个类问题(与上面描述的方法级别的性能问题) 。
这个文档讨论了
Android 系统是如何决定一个应用没有响应的,并提供了指引来保障你的应用是响应灵敏的。
1)是什么引发了 ANR在
Android 系统上,应用的响应灵敏性由 Activity Manager 和 Window Manager systemservices 所监控,当它监测到如下的其中一个条件时,
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,因为它会产生一个新的屏,偷走任何用户正在运行的应用的焦点。
对 Intentbroadcast 作出的响应,假如你的应用需要向用户显示什么东西,应该用 NotificationManager 来完成。
增强响应灵敏性通常,在一个应用中,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 屏任何值得使用的应用都可能会有几个不同的屏幕。
当设计你的 UI 屏幕时, 请一定要运用多个Activity 对象实例。
依赖于你的开发背景,你可能像解释某些类似 Java Applet 的东.