内存优化
Android系统对每个软件所能使用的RAM空间进行了限制(如:Nexus one 对每个软件的内存限制是24M),同时Java语言本身比较消耗内存,dalvik虚拟机也要占用一定的内存空间,所以合理使用内存,彰显出一个程序员的素质和技能。
1) 了解JIT
即时编译(Just-in-time Compilation,JIT),又称动态转译(Dynamic Translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。即时编译前期的两个运行时理论是字节码编译和动态编译。Android原来Dalvik虚拟机是作为一种解释器实现,新版(Android2.2+)将换成JIT编译器实现。性能测试显示,在多项测试中新版本比旧版本提升了大约6倍。
避免创建不必要的对象
就像世界上没有免费的午餐,世界上也没有免费的对象。虽然gc为每个线程都建立了临时对象池,可以使创建对象的代价变得小一些,但是分配内存永远都比不分配内存的代价大。如果你在用户界面循环中分配对象内存,就会引发周期性的垃圾回收,用户就会觉得界面像打嗝一样一顿一顿的。所以,除非必要,应尽量避免尽力对象的实例。下面的例子将帮助你理解这条原则:当你从用户输入的数据中截取一段字符串时,尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。这样你就有一 个新的String对象,它与原始数据共享一个char数组。 如果你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer,那么,请改变这个函数的参数和实现方式, 直接把结果附加到StringBuffer中,而不要再建立一个短命的临时对象。
一个更极端的例子是,把多维数组分成多个一维数组:
int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比 (int,int)对象数组性能要好很多。同理,这试用于所有基本类型的组合。如果你想用一种容器存储(Foo,Bar)元组,尝试使用两个单独的 Foo[]数组和Bar[]数组,一定比(Foo,Bar)数组效率更高。(也有例外的情况,就是当你建立一个API,让别人调用它的时候。这时候你要注重对API接口的设计而牺牲一点儿速度。当然在API的内部,你仍要尽可能的提高代码的效率)
总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。
尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。
总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。
减少不必要的全局变量
尽量避免static成员变量引用资源耗费过多的实例,比如Context。Android提供了很健全的消息传递机制(Intent)和任务模型(Handler),可以通过传递或事件的方式,防止一些不必要的全局变量。
不要过多指望gc
Java的gc使用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开销是可想而知。所以不要过多指望gc。将不用的对象可以把它指向NULL,并注意代码质量。同时,显示让系统gc回收,例如图片处理时,
if(bitmap.isRecycled()==false) { //如果没有回收 bitmap.recycle(); }
尽量避免随意使用静态变量
要知道,当某个对象被定义为stataic变量所引用,那么gc通常是不会回收这个对象所占有的内存,如
public class A{ static B b = new B(); }
将成员缓存到本地
访问成员变量比访问本地变量慢得多,下面一段代码:
for(int i =0; i <this.mCount; i++) { dumpItem(this.mItems); }
最好改成这样:
int count = this.mCount; Item[] items = this.mItems; for(int i =0; i < count; i++) { dumpItems(items); }
另一个相似的原则是:永远不要在for的第二个条件中调用任何方法。如下面方法所示,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大很多。
for(int i =0; i < this.getCount(); i++) { dumpItems(this.getItem(i)); }
同样如果你要多次访问一个变量,也最好先为它建立一个本地变量,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) { if(isHorizontalScrollBarEnabled()) { intsize = mScrollBar.getSize(false); if(size <=0) { size = mScrollBarSize; } mScrollBar.setBounds(0, height - size, width, height); mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false); mScrollBar.draw(canvas); } }
这里有4次访问成员变量mScrollBar,如果将它缓存到本地,4次成员变量访问就会变成4次效率更高的栈变量访问。
如果mScrollBar 只是 一个 基本数据类型,直接访问是可以接受的; 但如果mScrollBar 是一个 复杂的对象,需要 复制过来比较好, 因为如果访问的话,
另外就是方法的参数与本地变量的效率相同。
尽量早释放无用对象的引用
大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为null。
Public void test(){ Object obj = new Object(); …… Obj=null; }
上面这个就没必要了,随着方法test()的执行完成,程序中obj引用变量的作用域就结束了。但是如果是改成下面:
Public void test(){ Object obj = new Object(); …… Obj=null; //执行耗时,耗内存操作;或调用耗时,耗内存的方法 …… }
这时候就有必要将obj赋值为null,可以尽早的释放对Object对象的引用。
尽量缓存经常使用的对象
尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。
尽量避免非常大的内存分配
有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。
缓存
适量使用缓存,不要过量使用,因为内存有限,能保存路径地址的就不要存放图片数据,不经常使用的尽量不要缓存,不用时就清空。
Android优化-与Java有关-内存