`
mmdev
  • 浏览: 12952265 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Andorid性能优化设计

 
阅读更多
这篇文章说性能设计,我估摸着有很多童鞋都没看到过原文,这里推荐下,文章来自Android官方,在下载的AndroidDocsDevGuide可以看到。如果你没读过这篇文章,那么我强烈建议去细读它。


看到了吗?BestPractices,最佳实践!我很惭愧做了这么久开发也是在一个偶然的机会才关注到它!请猛击下面链接:

http://developer.android.com/guide/practices/design/performance.html(原文)

http://wikidroid.sinaapp.com/Guide/practices/design/performance(中文翻译)

为了方便阅读我将文章转载如下(字体加粗部分)。

英文原文:http://developer.android.com/guide/practices/design/performance.html译者署名:曲天译者链接:http://androidlearner.net


一个Android应用程序运行在有着有限的计算能力和存储空间及受限的电池寿命的移动设备上。有鉴于此,该应用程序应该是高效的。即便你的程序看起来运行得“足够快”,电池寿命可能是你想要优化你的应用程序的一个原因。对用户来说电池寿命重要,Android的电池耗尽意味着用户将知道是你的应用程序让电池耗尽的。

请注意,虽然本文主要是讲解微优化的,但是这些优化几乎不会破坏你的应用程序。你首要的任务依然是选择合适的算法和数据结构,但这些不在本文讨论的范围之内。

目录

[隐藏]

简介

以下是编写高效代码的两条基本原则:

  • 不要做不必要的事
  • 不要分配不必要的内存

明智地进行优化

这份文档是关于Android平台微优化的,这里假定你已经分析了程序知道了那些代码是需要优化的,并且你已经有了一个方法用于衡量你所做的任何改变产生的结果(好或坏)。你只有这么点工程时间用来投资,重要的是你要知道你明智使用了它们。

(参见#结束语可以了解更多的分析应用程序和编写高效的测试标准的技巧)

同时该文档假定你选择了正确的数据结构和算法,并且考虑到了改变你的程序将带来的性能影响。正确地选用数据结构和算法带来的影响与本文档提供所有建议相比将有很大不同,而预知你的改变带来的性能影响将有助于你切换到更好地实现方案(比起应用代码,这对类库代码更重要些)。

(如果你需要这方面的建议,可以参考Josh Bloch编写书籍Effective Java, 第47条)

在对你的Android应用程序进行微优化时,你将会遇到一个棘手的问题:你要保证你的应用程序运行在多个硬件平台上。不同版本的虚拟机运行在不同的处理器上其运行速度是不同的。你可以说“设备X比设备Y快/慢F倍”,但这个结论是不可以从一台设备推广到其他设备的。特别的是,你在模拟器上的测试结果并不能告诉你在其他任何设备中的性能怎么样。同样,使用JIT的设备和不使用JIT的设备也有相当大的差异:在使用了JIT的设备上运行得性能“最好”的代码可能并不适用于没有使用JIT的设备。

如果你想知道你的应用程序在指定设备上运行的情况,你需要在该设备上做测试。

避免创建不必要的对象

创建对象并非是免费的。虽然GC为每个线程都建立了临时对象池,可以使创建对象的代价变得小一些,但是分配内存永远都比不分配内存的代价大。

如果你在用户界面循环中分配对象内存,就会引发周期性的垃圾回收,用户就会觉得界面像打嗝一样一顿一顿的。Android2.3引入的并发收集器虽有助于垃圾回收,但不必要的工作还是应该避免。

所以,应尽量避免创建不必要的对象实例。下面的例子将有助你理解这条原则:

  • 如果你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer,那么,请改变这个函数的参数和实现方式,直接把结果附加到StringBuffer中,而不要再建立一个短命的临时对象。
  • 当你从用户输入的数据中截取一段字符串时,尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。这样你就有一个新的String对象,它与原始数据共享一个char数组。(后果就是如果你只是用原始输入的一小部分,那么使用这种方式你将一直把它保留在内存里)

一个更极端的例子是,把多维数组分成多个一维数组。

  • int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比(int,int)对象数组性能要好很多。同理,这试用于所有基本类型的组合。
  • 如果你想用一种容器存储(Foo,Bar)元组,尝试使用两个单独的Foo[]数组和Bar[]数组,一定比(Foo,Bar)数组效率更高。(也有例外的情况,就是当你建立一个API,让别人调用它的时候。这时候你要注重对API借口的设计而牺牲一点儿速度。当然在API的内部,你仍要尽可能的提高代码的效率)

总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。

性能优化的神话

我们将在这里澄清这份文档先前版本造成的误导性声明。

在不使用JIT的设备中,调用一个接口的引用会比调用实体类的引用花费更多的时间(举个例子,调用一个HashMap类型的map比调用一个Map类型的map代价要低,即便在这两种情况下,map都是HashMap的实例)。这里并非是先前说的慢2倍,实际上是更像是慢了6%。此外,JIT有效的区分了两者。

在不使用JIT的设备中,访问缓存的局部变量比重复调用该成员变量快大约20%。在使用JIT的设备中,访问局部变量的花费跟访问成员变量的花费是一样的,所以这并不是一个很好的优化措施,除非你觉得这样会使你的代码更容易阅读(这条建议对final、static和final static 修饰的成员变量也是适用的)。

用静态方法比虚方法好

如果你不需要访问一个对象的成员变量,那么请把方法声明成static。使用静态方法调用将快15-20%。这是一个很好的技巧,因为你可以通过方法签名调用该函数而不会改变对象的状态。

不要在类内部使用getter和setter

在很多本地语言如C++中,使用getter(比如:i = getCount())而不是直接访问成员变量(i = mCount)是一种很普遍的用法。在C++中这是一个非常好的习惯,因为编译器能够内联访问,如果你需要约束或调试变量,你可以在任何时候添加代码。

在Android上,这就不是个好主意了。虚方法的开销比直接访问成员变量大得多。设计公共的接口时,可以遵循面向对象的普遍做法来定义getters和setters,但是在一个类内部,你应该直接访问变量。

在没有JIT的设备上,直接访问成员变量比调用一个简单的getter方法快大约3倍。在有JIT的设备上,直接访问成员变量比调用一个简单的getter方法快大约7倍。目前这是Froyo(android2.2)版本上的事实,我们将在后续版本上改善JIT内联getter方法的效率。

使用Static Final 修饰常量

让我们来看看这两段在类前面的声明:

static int intVal = 42;
static String strVal = "Hello, world!";

编译器会生成一个叫做<clinit>的初始化类的方法,当类第一次被使用的时候这个方法会被执行。该方法会将42赋给intVal,然后把一个指向类中常量表的引用赋给strVal。当以后要用到这些值的时候,可以在成员变量表中查找到他们。

下面我们使用“final”关键字做些改进:

static final int intVal = 42;
static final String strVal = "Hello, world!";

现在,类不再需要<clinit>方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个相对来说代价小的字符串常量,而不是使用成员变量(要注意的是这个优化只适用于基本类型和字符串常量,而不适用引用类型。尽管如此,这依然是一种很提倡的做法,你应该尽可能地把常量声明为static final)。

使用for-each语法

加强的for循环(有时也叫for-each循环)可以用在实现了Iterable接口的集合类型和数组上。对集合来说,一个迭代器意味着可以调用hasNext()和next()方法。在ArrayList中,手写的计数循环比使用for-each循环快大约3倍,但是对其他集合类来说,foreach相当于使用 iterator。

下面展示了遍历一个数组的3种可选用法:

static class Foo {
         int mSplat;
     }
     Foo[] mArray = ...
 
public void zero() {
         int sum = 0;
         for (int i = 0; i < mArray.length; ++i) {
             sum += mArray[i].mSplat;
         }
     }
 
public void one() {
         int sum = 0;
         Foo[] localArray = mArray;
         int len = localArray.length;
 
for (int i = 0; i < len; ++i) {
             sum += localArray[i].mSplat;
         }
     }
 
public void two() {
         int sum = 0;
         for (Foo a: mArray) {
             sum += a.mSplat;
         }
     }

zero()最慢,因为循环的每一次迭代都会获取数组的长度一次,这一点JIT并没有优化。

one()比zero()要快些,它将所有变量用局部变量保存,避免了查找。但仅仅是存储数组长度会带来效率的提升。

two()使用了在java1.5中引入的foreach语法。对于没有JIT设备来说,这种方法是最快的,区分于使用了JIT的one()。

综上所述,默认可以使用加强型的for(即foreach),在影响关键性能的ArrayList遍历时,考虑手写计数循环。(可参考Effective Java第46条)

将与内部类一同使用的变量声明在包范围内

请看下面的类定义:

public class Foo {
     private class Inner {
         void stuff() {
             Foo.this.doStuff(Foo.this.mValue);
         }
     }
 
private int mValue;
 
public void run() {
         Inner in = new Inner();
         mValue = 27;
         in.stuff();
     }
 
private void doStuff(int value) {
         System.out.println("Value is " + value);
     }
 }

这其中的关键是,我们定义了一个私有内部类(Foo$Inner),它需要访问外部类的私有成员变量和函数。这是合乎语法的,并且会打印出我们希望的结果”Value is 27″。

问题在于Foo和Foo$Inner是两个不同的类,虚拟机认为在Foo$Inner类中直接访问Foo类的私有成员是是非法的,即便在java语言中允许内部类访问外部类的私有变量。为了跨越这一障碍,编译器会生成一组中间方法:

/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}

内部类在访问mValue或调用外部类的doStuff方法时,都会调用这些静态方法。也就是说,上面的代码说明了一个问题,你是在通过接口方法访问这些成员变量和函数而不是直接调用它们。在前面我们已经说过,使用接口方法(getter、setter)比直接访问速度要慢。所以这个例子就是在特定语法下面产生的一个“隐性的”性能障碍。

如果你的代码中是类似情况,你可以通过将内部类访问的变量和函数声明由私有范围改为包范围来避免开销。遗憾的是,这些域和方法可以被同一个包内的其他类直接访问,因此当你设计公共API的时候应该谨慎使用这条优化原则。

避免使用浮点数

从常理来讲,在Android设备上浮点数比整数慢2倍。在缺少FPU和JIT的G1和使用了FPU和JIT的Nexus,这都是事实。(当然,这两者之间的运算速度相差约10倍)

在速度方面,double和float在现代硬件面前并没有明显差异。在空间方面,double占用的空间是float的2倍。如果跟台式机一样你的空间不是问题,你可以使用double而非float。

同样,即便是整数,一些芯片对乘法有硬件支持而缺少对除法的支持。这种情况下,整数的除法和取模运算都是由软件来完成的——你可以想象下在设计哈希表或者做大量数学运算时做的那些事(就知道效率有多么低了)。

了解和使用Android提供的类库

除了有特别的原因,建议你尽可能使用android提供的类库而不是你自己的,要记住的是Android系统会对现有的java类库做一些优化和扩展,这显然比原有JIT类库更有效率些。一个典型的例子是Dalvik使用内联优化了String.indexOf方法,同样的还有System.arraycopy方法,后者比使用了JIT的Nexus One快大约9倍。(这条可参考Effective Java第47条)

谨慎使用JNI调用本地方法

本地代码不一定比java代码更有效率。首先从java到本地代码的过渡是需要开销的,而这点JIT并不能优化。如果在本地代码里你分配了本地资源(本地的内存,文件,或任何其他的资源),那么及时地搜集这些资源将变得更困难。同时,对不同的架构平台你需要编译不同的代码(而不是依赖JIT给你提供一套)。甚至对同一个平台,你也需要编译多种版本的代码。(G1的ARM处理器编译的本地代码跟 Nexus One上的ARM处理器编译的本地代码并不通用)

当你已经有一个本地代码库并想把代码用于android时,你可以考虑使用JNI调用本地代码。事实上,自java1.3以后,使用本地方法来提高性能的做法已经不值得提倡了。

如果你确实需要用到本地代码,可以参考JNI Tips。(参见Effective Java第54条)

结束语

最后一件事情:要不断的测试。要带着问题去优化,确保你能够精确的度量未优化之前的性能,否则你将不能衡量你尝试做出的性能优化。

该文档中每一条都有相应的标准来衡量,在code.google.com “dalvik” project能找到这些标准。

这些标准都是构建在Caliper的java框架上的。尽管如此,”微标准“很难衡量,这个项目 Caliper可以帮助你,我们在此强烈建议你使用该框架。

你可能还会发现可以用Traceview来分析程序,但要意识到目前它是禁用JIT的,这会导致计算JIT调用的时间是不正确的。要确保使用Traceview数据带来的优化确实要比不使用Traceview好。


文中提到的Android性能优化的原则和方法,阅读过后可能还是有些茫然。我们平常开发也就处理些简单的数据或者实现一个简单的功能,没有牵涉到算法、内部类等。那么如何优化我们的程序呢?下面我针对几个常用的编程方法进行代码的微优化,在做Android应用开发的时候我们也要谨记,手机或者MID内存是相当有限的,开发的应用程序应该尽量让其高效率的执行代码,而非像做J2EE程序似的铺张浪费。有很多童鞋将之前开发J2EE的经验带到Android应用开发上来,其实这些也只是细节,多注意多思考吧。

1、关于获取数组和列表长度的方法,请看下面的代码

  1. <spanstyle="font-size:16px;">List<String>list=newArrayList<String>();
  2. for(inti=0;i<100;i++){
  3. list.add("abcdefg");
  4. }
  5. longstartTime1=System.currentTimeMillis();
  6. for(inti=0;i<list.size();i++){
  7. System.out.println(list.get(i));
  8. }
  9. longendTime1=System.currentTimeMillis();
  10. Log.e(TAG,"time:"+(endTime1-startTime1));
  11. /////////////////////////////////////////////
  12. longstartTime2=System.currentTimeMillis();
  13. intsize=list.size();
  14. for(inti=0;i<size;i++){
  15. System.out.println(list.get(i));
  16. }
  17. longendTime2=System.currentTimeMillis();
  18. Log.e(TAG,"time:"+(endTime2-startTime2));</span>

通过上面简单的方法,结合打印信息,对比可知:先获取list的长度再循环效率高出一倍左右!那么在你的程序中知道该怎么做了吧?

2、关于Android静态变量问题

Android的静态变量有个特点,就是在关闭应用程序时静态变量任然存在,请看以下代码片段

  1. <spanstyle="font-size:16px;">privatestaticinti=10;
  2. @Override
  3. publicvoidonCreate(BundlesavedInstanceState){
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.main);
  6. Log.e(TAG,"i="+i);
  7. i=15;
  8. }
  9. @Override
  10. protectedvoidonDestroy(){
  11. Log.e(TAG,"onDestroy=========");
  12. //android.os.Process.killProcess(android.os.Process.myPid());
  13. super.onDestroy();
  14. }</span>

将应用程序运行两次,看到如下结果:

怎么样,有意思吧!为了避免这种情况,可在Activity.onDestroy方法中加入这句代码:android.os.Process.killProcess(android.os.Process.myPid());将当前应用所在的进程中给杀掉。如果不杀掉的话,那么要谨慎使用了static,就像上面的代码由于没有销毁而导致i的初始值不正确。当然还有另外一种方法达到static的效果。那就是继承Application来达到多个ActivityService中使用的目的。我们都知道,在Android应用程序中所有的ActivityServiceBroadcast间接继承Context。在AndroidManifest.xml配置文件中,所有的ActivityService等都被包含在标签Application中。所有的ActivityService都可调用Application中非私有的方法和变量。于是可以通过继承Application自定一个Application的子类来达到目的。当然了,只要控制好了就没什么问题。

3、关于Android的布局优化,请看下一篇博客吧,因为很重要,也有很多知识点~

原创文章,转载请注明出处:http://blog.csdn.net/tangcheng_ok



分享到:
评论

相关推荐

    Android性能优化学习手册

    1、性能优化专家:具备深度性能优化与体系化APM建设的能力。 2、架构师:具有丰富的应用架构设计经验与心得,对Android Framework层与热门三方库的实现原理与架构设计了如指掌。 3、音视频/图像处理专家:毫无疑问,...

    android 360°全方面性能调优.pdf

    android 性能优化,稳定性优化,功耗优化一本全,面试宝典。 1. 设计思想与代码优化 2.程序性能优化 3.内存优化 4.耗电优化 5.网络优化 6.应用apk优化 7.屏幕适配 8.启动速度优化 9.流畅度优化 10.ANR分析 11.crash...

    Android开发技巧与性能优化

    4.1 为性能设计: 4.2 为响应灵敏性设计 4.3 为无缝设计: 5. 多资源文件的引用 6. ANDROID 调试 LOGCAT 技巧 7. 用 ANDROID 运行最简单的C 程序 8. 开发技巧杂集 8.1 一些源于 CSS 的组合实现技巧 8.2 关于 SEARCH...

    Android性能优化典范(上)

    2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的AndroidApp。课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同时也介绍了如何...

    Android设计模式[蓝皮书]

    这篇文章应该是我转做Android 1年多第2次系统梳理设计模式的记录手稿,很全面,也有相应的案例讲解

    Android程序设计基础

    Android针对低能耗、低内存的设备进行了优化,这种根本性的优化是之前的平台从未尝试过的。  高质量的图形和声音。将类似于Flash的光滑、无锯齿的2D矢量图形和动画与3D加速的OpenGL图形相结合,可实现各种新式的...

    Android 项目优化、面试题集,包含Android、Java、数据结构、算法、个人blog备份等。.zip

    因此,我们整理了常用的Android开发工具集,包括开发工具、测试工具、性能优化工具等。这些工具都是经过我们精心筛选和测试的,能够帮助开发者们更加高效地进行Android开发工作。 总的来说,这份Android项目资源包...

    Android DevCamp幻灯片分享:凡客移动应用之Android + HTML5技术运用 | 凡客 汪健飞 徐金山

    现主要负责凡客诚品Android客户端的性能优化、架构优化。酷爱Android开发,技术控,喜欢将移动电商产品优化到极致。 (2)徐金山:现任凡客诚品Android客户端研发经理,5年JAVA技术开发经验,3年Android开发经验。...

    Android期末设计报告.docx

    4 性能需求 4.1用户登录界面 Android期末设计报告全文共9页,当前为第5页。Android期末设计报告全文共9页,当前为第5页。用户登录游戏界面,输入用户名和密码,通过数据库验证后登录游戏主界面。 Android期末设计...

    Android应用开发,完整扫描版

    《Android应用开发》还对Android平台下开发环境的搭建、程序的签名和发布以及程序调试和性能优化所用到的工具和方法等进行了讲解。 《Android应用开发》适合有一定程序设计基础并致力于从事Android平台应用软件开发...

    Android应用开发

    《Android应用开发》还对Android平台下开发环境的搭建、程序的签名和发布以及程序调试和性能优化所用到的工具和方法等进行了讲解。, 《Android应用开发》适合有一定程序设计基础并致力于从事Android平台应用软件开发...

    Android UI基础教程 高清带标签pdf

    本书不仅会告诉读者创建灵活布局的最佳方法,还会帮助开发者优化已有的UI界面,从而使他们的应用运行更加流畅,拥有最佳性能。学习完本书,读者可以自己创建一个成功的Android应用的UI界面。 《AndroidUI基础教程》...

    BAT大咖助力 全面升级Android面试

    第8章 Android异常与性能优化相关面试问题 第9章 热门前沿知识相关面试问题 第10章 Java高级技术点面试问题 第11章 设计模式相关面试问题 第12章 网络协议相关面试问题 第13章 算法相关面试问题 第14章 课程总结

    基于Android系统的智能社区平台系统APP设计与实现.docx

    2.4 Android应用程序性能优化 13 第三章 智能社区平台系统设计 15 3.1 系统需求分析 15 3.2 系统总体设计 16 3.3 系统模块设计 17 3.4 系统数据库设计 19 第四章 智能社区平台系统APP实现 21 4.1 系统框架搭建 21 ...

    Android应用开发揭秘pdf高清版

    不仅详细讲解了Android框架、Android组件、用户界面开发、游戏开发、数据存储、多媒体开发和网络开发等基础知识,而且还深入阐述了传感器、语音识别、桌面组件开发、Android游戏引擎设计、Android应用优化、OpcnGL等...

    《Android应用开发揭秘》附带光盘代码.

     第3章 Android程序设计基础  3.1 Android程序框架  3.1.1 Android项目目录结构  3.1.2 Android应用解析  3.2 Android的生命周期  3.3 Android程序U设计  3.4 小结  第4章 用户界面开发  4.1 用户界面开发...

    Android Binder设计与实现

    深入了解Binder并将之与传统 IPC做对比有助于我们深入领会进程间通信的实现和性能优化。本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信模型和 Binder通信协议了解Binder的设计需求;然后分别...

    Android社交系统设计与实现:构建高效移动应用

    此外,你还将学习到如何优化应用性能,提供更好的用户体验。 阅读建议:在学习过程中,建议你结合实际的开发环境进行实践操作,以更好地理解和掌握教程中的设计思想和技术要点。对于关键的技术和方法,多做一些实战...

Global site tag (gtag.js) - Google Analytics