Android 性能优化

#Android 性能优化

原文:http://hukai.me/android-training-course-in-chinese/performance/memory.html

###管理应用的内存
Random Access Memory (RAM 随机存取存贮器) 在任何软件开发环境中都是一个非常宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。尽管Android的Dalvik虚拟机扮演了常规的垃圾回收的角色,但这并不意味着你可以忽视app的内存分配与释放的时机与地点。

为了GC能够从app中及时回收内存,我们需要注意避免内存泄露(通常由于在全局成员变量中持有对象引用而导致)并且在适当的时机(下面会讲到的lifecycle callbacks)来释放引用对象。对于大多数app来说,Dalvik的GC会自动把离开活动线程的对象进行回收。

#####第一部分:Android 是如何管理内存的
Android 并没有为内存提供交换区(Swap space),但是它使用paging 与 memory-mapping(mmapping) 的机制来管理内存。这意味着任何你修改的内存(无论是通过分配新的对象还是去访问mmaped pages 中的内容)都会贮存在RAM中,而且不会被paged out 。因此唯一释放内存的方法是释放那些你能hold住的对象的引用,但这个对象没有被任何其他对象所引用时,它就能够被GC回收了。只有一个例外是,如果系统向要在其他地方重用这个对象

1)共享内存

Android 通过下面几种方式在不同的进程中来实现共享RAM

  • 每一个app的进程都是从一个叫做 Zygote 的进程中fork(翻译:分叉)出来的。Zygote 进程在系统启动并且载入通用的 Framework的代码与资源之后开始启动。为了启动一个新的进程,系统会 fork Zygote进程 生成一个新的进程,然后在新的进程中加载并运行app代码。这是的大多数的 Ram pages 被用来分配 给Framework 的代码,同时使得 RAM 的资源能够在应用的所有进程中进行共享
  • 大多数static 的数据被 mmapped(内存映射) 到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被 paged out,例如下面几种static 数据:
    • Dalik 代码(放在一个预链接的 .odex 文件中以便于直接mapping)
    • APP resources (通过把资源表结构设计成便于mmapping的数据结构,另外还可以通过把APK中的文件做aligning的操作来优化)
    • 传统项目元素,比如 .so 文件中的本地代码.
  • 在很多情况下,Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现一些动态RAM区域能够在不同进程间进行共享。例如,window surfaces在app与screen compositor之间使用共享的内存,cursor buffers在content provider与client之间使用共享的内存。

#####分配与回收内存

这里有下面几点关于Andorid 如何分配 和回收内存的事实:

未完,待续。。。。。。

###代码性能优化建议

通常来说, 高效的代码需要满足下面两条原则:

  • 不要做冗余的工作
  • 尽量避免执行过多的内存分配操作

在优化App时其中一个难点就是让App能在各种型号的设备上运行。不同版本的虚拟机在不同的处理器上会有不同的运行速度。你甚至不能简单的认为“设备X的速度是设备Y的F倍”,然后还用这种倍数关系去推测其他设备。另外,在模拟器上的运行速度和在实际设备上的速度没有半点关系。同样,设备有没有JIT也对运行速度有重大影响:在有JIT情况下的最优化代码不一定在没有JIT的情况下也是最优的。

为了确保App在各设备上都能良好运行,就要确保你的代码在不同档次的设备上都尽可能的优化。

#####避免创建不必要的对象

创建对象从来不是免费的。Generational GC 可以使临时对象的分配变得廉价一些。但是分配内存总是比不分配操作更昂贵

随着你在App中分配更多的对象,你可能需要强制gc,而gc操作会给用户体验带来一点点卡顿。虽然从Android 2.3开始,引入了并发gc,它可以帮助你显著提升gc的效率,减轻卡顿,但毕竟不必要的内存分配操作还是应该尽量避免。

因此请尽量避免创建不必要的对象,有下面一些例子来说明这个问题:

  • 如果你需要返回一个String对象,并且你知道它最终会需要连接到一个StringBuffer,请修改你的函数实现方式,避免直接进行连接操作,应该采用创建一个临时对象来做字符串的拼接这个操作。
    当从已经存在的数据集中抽取出String的时候,尝试返回原数据的substring对象,而不是创建一个重复的对象。使用substring的方式,你将会得到一个新的String对象,但是这个string对象是和原string共享内部char[]空间的。

一个稍微激进点的做法是把所有多维的数据分解成一维的数组:

  • 一组int数据要比一组Integer对象要好很多。可以得知,两组一维数组要比一个二维数组更加的有效率。同样的,这个道理可以推广至其他原始数据类型。
  • 如果你需要实现一个数组用来存放(Foo,Bar)的对象,记住使用Foo[]与Bar[]要比(Foo,Bar)好很多。(例外的是,为了某些好的API的设计,可以适当做一些妥协。但是在自己的代码内部,你应该多多使用分解后的容易)。

通常来说,需要避免创建更多的临时对象。更少的对象意味者更少的gc动作,gc会对用户体验有比较直接的影响。

#####选择 Static 而不是 irtual

如果你不需要访问一个对象的值,请保证这个方法是static类型的,这样方法调用将快15%-20%。这是一个好的习惯,因为你可以从方法声明中得知调用无法改变这个对象的状态。

#####常量声明为 Static Final

考虑下面这种声明方式:

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

编译器会使用一个初始化类的函数,然后当类第一次被使用的时候执行。这个函数将42存入intVal,还从class文件的常量表中提取了strVal的引用。当之后使用intVal或strVal的时候,他们会直接被查询到。

我们可以用 final 关键字优化:

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

这时再也不需要上面的方法了,因为final声明的常量进入了静态dex文件的域初始化部分。调用intVal的代码会直接使用42,调用strVal的代码也会使用一个相对廉价的“字符串常量”指令,而不是查表。

注意:这个方案只对原始类型和String 类型有效, 而不是任意的引用类型,不过,在必要时使用 static final 是个很好的习惯

避免内部的 getter 和 setter

像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.

#####使用增强 for循环

增强for 循环(也称 for-each循环) 可以被用在实现了 iterable 接口的collections 以数组上.使用collection 的时候,iterator 会被分配, 用于for-each 调用 hasnext() 和 next() 方法. 使用ArrayList 时,手写的计数式for循环会快3倍(管不有没有 JIT),但是对于其他colllection ,增强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.lentgh;
	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 循环,但是对于 ArrayList ,请使用方法 one()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值