这里根据网络上各位大神已经总结的知识内容做一个大汇总,作为记录,方便后续“温故知新”。
性能指标:
(1)使用流畅度:
图片处理器每秒刷新的帧数(FPS),可用来指示页面是否平滑的渲染。高的帧率可以得到更流畅,更逼真的动画,不过帧率达到60fps以上,人眼主观感受到的差别就不大了。所以以60fps作为衡量标准,即要求每一帧刷新的时间小于16ms,这样才能保证滑动中平滑的流畅度。
(2)内存使用情况:
在android系统中,每个APP进程除了同其他进程共享(shared dirty)外,还独用私有内存(private dirty),通常我们使用PSS(=私有内存+比例分配共享内存)来衡量一个APP的内存开销。移动设备的内存资源是非常有限,为每个APP进程分配的私有内存也是有限制。一方面我们要合理的申请内存使用,以免导致频繁的GC影响性能和大对象申请发生内存溢出;另一方面,我们要及时释放内存,以免发生内存泄漏。
(3)电量使用情况:
相对于PC来说,移动设备的电池电量是非常有限的,保持持久的续航能力尤为重要。另外,android的很多特性都比较耗电(如屏幕,GPS,sensor传感器,唤醒机制,CPU,连网等的使用),我们必须要慎重检查APP的电量使用,以免导致用户手机耗电发热,带来不良体验。
(4)流量使用情况:
目前的网络类型包含2G3G4Gwifi,其中还有不同运营商的区分,我们在APP的使用中经常遇到大资源,重复请求,调用响应慢,调用失败等各种情况。在不同的网络类型之下,我们不仅要控制流量使用,还需要加快请求的响应。
原理以及优化技术要点
原理及相关知识介绍请参考google官方的教程:
android app性能优化大汇总(google官方Android性能优化典范 - 第1季)
android app性能优化大汇总(google官方Android性能优化典范 - 第2季)
android app性能优化大汇总(google官方Android性能优化典范 - 第3季)
具体优化方案:
android app性能优化大汇总(UI渲染性能优化)
android app性能优化大汇总(内存性能优化)
代码编写风格细节对性能的影响:
1、使用优化后的数据容器:
请使用 Andorid 框架中优化过的数据容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray。类似于 HashMap 这一类的容器的效率不是很高,因为在每个 Map 中对于每一次的存放数据,他都需要独立一个单独的 Entry 对象进行传芳。而 SparseArray 由于禁止系统自动封装键值对,因此他更加有效率。并且你不需要担心丢失掉原有信息。
2、尽量避免使用枚举类型:
枚举与静态常量相比,通常会消耗两倍的内存资源。
3、避免创建不必要的对象:(特别是在onDraw类似的函数里)
创建太多的对象会造成性能低下,这谁都知道,可是为什么呢?首先分配内存本身需要时间,其次虚拟机运行时堆内存使用量是有上限的,当使用量到达一定程度时会触发垃圾回收,垃圾回收会使得线程甚至是整个进程暂停运行。可想而知,如果有对象频繁的创建和销毁,或者内存使用率很高,就会造成应用程序严重卡顿。(内存抖动)
4、合理使用static成员:
主要有三点需要掌握:
(1)如果一个方法不需要操作运行时的动态变量和方法,那么可以将方法设置为static的。
(2)常量字段要声明为“static final”,因为这样常量会被存放在dex文件的静态字段初始化器中被直接访问,否则在运行时需要通过编译时自动生成的一些函数来初始化。此规则只对基本类型和String类型有效。
(3)不要将视图控件声明为static,因为View对象会引用Activity对象,当Activity退出时其对象本身无法被销毁,会造成内存溢出。
5、 使用for-each循环:
增强的For循环(也被称为 for-each 循环)可以被用在实现了 Iterable 接口的 collections 以及数组上。使用collection的时候,Iterator (迭代器,译者注) 会被分配,用于for-each调用 hasNext()
和 next()
方法。使用ArrayList时,手写的计数式for循环会快3倍(不管有没有JIT),但是对于其他collection,增强的for-each循环写法会和迭代器写法的效率一样。
请比较下面三种循环的方法:
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()稍微快些。
-
two() 在没有做JIT时是最快的,可是如果经过JIT之后,与方法one()是差不多一样快的。它使用了增强的循环方法for-each。
所以请尽量使用for-each的方法,但是对于ArrayList,请使用方法one()。
你还可以参考 Josh Bloch 的 《Effective Java》这本书的第46条
6、避免使用浮点类型:
经验之谈,所在Android设备中浮点型大概比整型数据处理速度慢两倍,以如果整型可以解决的问题就不要用浮点型。另外,一些处理器有硬件乘法但是没有除法,这种情况下除法和取模运算是用软件实现的。为了提高效率,在写运算式时可以考虑将一些除法操作直接改写为乘法实现,例如将“x / 2”改写为“x * 0.5”。
7、了解并使用库函数:
Java标准库和Android Framework中包含了大量高效且健壮的库函数,很多函数还采用了native实现,通常情况下比我们用Java实现同样功能的代码的效率要高很多。所以善于使用系统库函数可以节省开发时间,并且也不容易出错。
8、避免依赖注入框架:
使用类似于 Guice 和 RoboGuice 的依赖注射框架,或许会使你的代码变得更加漂亮,因为他们能够减少你需要写的代码,并且为测试或者在其他条件改变的情况下,提供一种自适应的环境。但是,这些框架在初始化的时候会因为注释而消耗大量的工作在扫描你的代码上,这会让你的代码在进行内存映射的时候花费更多的资源。虽然这些内存能够被 Android 进行回收,但是等待整个分页被释放需要很长一段时间。
9、小心使用外部依赖包:
很多依赖包都不是专门为了移动环境或者移动客户端写的。如果你决定使用一个外部依赖包,你应该提前明白你需要为了将它移植到移动端而消耗花费大量的时间和工作量。请在使用外部依赖包得时候提前分析他的代码和内存占用
即使依赖包是为了 Android 而设计的,但是这也有潜伏的危险,因为每一个包都做着不同的工作。例如,有一个依赖包使用纳米级的 protobufs 但是别的包使用微米级的 protobufs.那么现在在你的应用中就有两套 protobuf 的标准了。这会在你记录数据,分析数据,加载图像,缓存,或者其他任何可能的情况下发生你不希望发生的事情。ProGuard 无法在这里帮助你,因为他们都是你所依赖包的底层实现,。当你使用从别的依赖包(他可能继承了很多的依赖包)里继承的 Activity 时,这个问题变得尤其严重,当你使用反射以及干别的事情的时候
请注意不要落入一个依赖包的陷阱,你不希望引入一大片你根本不会使用到的代码。如果你无法找到一种已经实现的逻辑来完全满足你的需求,那么你尽量创建一个自己的实现方式。
10、避免内部的Getters/Setters:
像C++等native language,通常使用getters(i = getCount())而不是直接访问变量(i =mCount)。这是编写C++的一种优秀习惯,而且通常也被其他面向对象的语言所采用,例如C#与Java,因为编译器通常会做inline访问,而且 你需要限制或者调试变量,你可以在任何时候在getter/setter里面添加代码。然而,在Android上,这是一个糟糕的写法。虚函数的调用比起直接访问变量要耗费更多。在面向对象编程中,将getter和setting暴露给公用接口是合理的,但在类内部应该仅仅使用域直接访问。在没有JIT(Just In Time Compiler)时,直接访问变量的速度是调用getter的3倍。有JIT时,直接访问变量的速度是通过getter访问的7倍。请注意,如果你使用 ProGuard , 你可以获得同样的效果,因为ProGuard可以为你inline accessors.