Android性能优化之内存优化

1. 内存优化

   Android系统对每个软件所能使用的RAM空间进行了限制,同时Java语言本身比较消耗内存,dalvik虚拟机也要占用一定的内存空间,所以合理使用内存,彰显出一个程序员的素质和技能。

1) 了解JIT

   即时编译(Just-in-time Compilation,JIT),又称动态转译(Dynamic Translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。即时编译前期的两个运行时理论是字节码编译和动态编译。Android原来Dalvik虚拟机是作为一种解释器实现。新版本(Android2.2+)将换成JIT编译器实现。性能测试显示,在多项测试中新版本比旧版本提升了大约6倍。

2) 避免创建不必要的对象

   就像世界上没有免费的午餐,世界上也没有免费的对象。虽然gc为每个线程都建立了临时对象池,可以使创建对象的代价变得小一些,但是分配内存永远都比不分配内存的代价大。如果你在用户界面循环中分配对象内存,就会引发周期性的垃圾回收,用户就会觉得界面像打嗝一样一顿一顿的。所以,除非必要,应尽量避免创建对象的实例。下面的例子将帮助你理解这条原则:当你从用户输入的数据中截取一段字符串时,尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。这样你就有一个新的String对象,它与原始数据共享一个char数组。如果你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer,那么,请改变这个函数的参数和实现方式,直接把结果附加到StringBuffer中,而不要再建立一个短命的临时对象。

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

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

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

3) 静态方法代替虚拟方法

   如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。

4) 避免内部Getters/Setters

   在原生语言像C++中,通常做法是用Getters(i=getCount())代替直接字段访问(i=mCount)。这是C++中一个好的习惯,因为编译器会内联这些访问,并且如果需要约束或者调试这些域的访问,你可以在任何时间添加代码。而在Android中,这不是一个好的做法。虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。无JIT时,直接字段访问大约比调用getter访问快3倍。有JIT时(直接访问字段开销等同于局部变量访问),要快7倍。

5) 将成员缓存到本地

   访问成员变量比访问本地变量慢得多,下面一段代码:

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次效率更高的栈变量访问。
   另外就是方法的参数与本地变量的效率相同。

6) 其他方式

  • 对常量使用static final修饰符
  • 使用改进的for循环语法
  • 避免使用浮点数
  • 复杂算法尽量用C完成
  • 减少不必要的全局变量
  • 不要过多的指望gc
  • 使用实体类比接口好
  • 避免使用枚举
  • 关闭资源对象

2. 视图优化

1) View优化

  • 减少不必要的View以及View的嵌套层次:比如实现一个listview中常用的layout,可以使用RelativeLayout减少嵌套,要知道每个View的对象会耗费 1~2k内存,嵌套层次过多会引起频繁的gc,造成ANR。

  • 通过HierarchyViewer查看布局结构:利用HierarchyViewer来查看View的结构:~/tools/hierarchyviewer,能很清楚地看到RelativeLayout下面的扁平结构,这样能加快dom的渲染速度。

  • 通过Layoutopt优化布局:通过Android sdk中tools目录下的layoutopt 命令查看你的布局是否需要优化。详细请参考http://apps.hi.baidu.com/share/detail/34247942

2) 多线程解决复杂计算

   占用CPU较多的数据操作尽可能放在一个单独的线程中进行,通过handler等方式把执行的结果交于UI线程显示。特别是针对的网络访问,数据库查询,和复杂的算法。目前Android提供了AsyncTask,Hanlder、Message和Thread的组合。对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过concurrent类的线程池解决线程创建的效率瓶颈。另外值得注意的是,应用开发中自定义View的时候,交互部分,千万不要写成线程不断刷新界面显示,而是根据TouchListener事件主动触发界面的更新。

3) 布局用Java比XML快

   一般情况下对于Android程序布局往往使用XML文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以及执行效率,可以通过Java代码执行创建,虽然Android编译过的XML是二进制的,但是加载XML解析器的效率对于资源占用还是比较大的,Java处理效率比XML快得多,但是对于一个复杂界面的编写,可能需要一些套嵌考虑,如果你思维灵活的话,使用Java代码来布局你的Android应用程序是一个更好的方法。

4) 对大型图片进行缩放

   图片读取是OOM(Out of Memory)的常客,当在Android手机上直接读取4M的图片时,死神一般都会降临,所以导致往往自己手机拍摄的照片都不能直接读取。对大型图片进行缩放处理图片时我们经常会用到BitmapFactory类,android系统中读取位图Bitmap时分给虚拟机中图片的堆栈大小只有8M。用BitmapFactory解码一张图片时,有时也会遇到该错误。这往往是由于图片过大造成的。这时我们需要分配更少的内存空间来存储。BitmapFactory.Options.inSampleSize设置恰当的inSampleSize可以使BitmapFactory分配更少的空间以消除该错误。Android提供了一种动态计算的,如下:读取图片之前先查看其大小:

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);

   使用得到的图片原始宽高计算适合自己的samplesize

BitmapFactory.Options opts = new BitmapFactory.Options();
 opts.inSampleSize = 4 ;// 4就代表容量变为以前容量的1/4
 Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts); 

   对于过时的Bitmap对象一定要及时recycle,并且把此对象赋值为null。

bitmap.recycle();
bitmap = null; 

5) 合理使用ViewStub

          ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。当ViewStub可见,或者调用 inflate()函数时,才会加载这个布局资源文件。 该ViewStub在加载视图时在父容器中替换它本身。因此,ViewStub会一直存在于视图中,直到调用setVisibility(int) 或者inflate()为止。ViewStub的布局参数会随着加载的视图数一同被添加到ViewStub父容器。同样,你也可以通过使用 inflatedId属性来定义或重命名要加载的视图对象的Id值。所以我们可以使用ViewStub延迟加载某些比较复杂的layout,动态加载 View,采用ViewStub 避免一些不经常的视图长期握住引用。

6) 针对ListView的性能优化

  • 复用convertView

  • 在getItemView中,判断convertView是否为空,如果不为空,可复用。如果convertView中的view需要添加listener,代码一定要在if(convertView==null){}之外。

  • 异步加载图片,item中如果包含有web image,那么使用异步加载

  • 快速滑动时不显示图片

  • 当快速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状 态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来。

  • item尽可能的减少使用的控件和布局的层次;背景色与cacheColorHint设置相同颜色;ListView中item的布局至关重要,必须尽可 能的减少使用的控件,布局。             

  • RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少 ListView的内存使用,减少滑动时gc次数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。

  • getView优化:ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用view;getView方法中不能做复杂的逻辑计算,特别是数据库和网络访问操作,否则会严重影响滑动时的性能

7) 其他

i.    分辨率适配

        -ldpi,-mdpi, -hdpi配置不同精度资源,系统会根据设备自适应,包括drawable, layout,style等不同资源。

                   ii.    尽量使用dp(density independent pixel)开发,不用px(pixel)。

                   iii.    多用wrap_content, fill_parent

                   iv.    抛弃AbsoluteLayout

                   v.    使用9patch(通过~/tools/draw9patch.bat启动应用程序),png格式

                   vi.    采用 优化布局层数;采用来共享布局。

                   vii.    将Acitivity中的Window的背景图设置为空。getWindow().setBackgroundDrawable(null);android的默认背景是不是为空。

                   viii.    View中设置缓存属性.setDrawingCache为true。

3 网络优化

  • 避免频繁网络请求
  • 数据压缩
  • 使用线程池
  • 选择合适的数据传输方式









你可能感兴趣的:(Android性能优化)