Android编码优化

来源:互联网 发布:java项目开发硬件环境 编辑:程序博客网 时间:2024/05/16 14:12

Android优化总结

一、使用正确的FOR循环

for ( int i=0, n=list.size();i<n; i++)

{

 ...//Arraylist中使用最为高效

}

for (Object obj : list)

{

 ...//在Arraylist以外的结构,例如collection使用高效

for (int i=0;i<list.size(); i++)

{

 ...//最低效

}

 

二、避免创建不必要的对象

1.避免创建重复对象

for (int i=0,n=list.size(); i<n; i++)

{

  Fooobj = new Foo();

 obj.setXXX(list.get(i));

 ....

}

在不影响功能的情况下,可以使用

Foo obj = newFoo();

for (int i=0,n=list.size(); i<n; i++)

{

  obj.setXXX(list.get(i));

 ....

}

因为每一次new的代价都是很高的,因为可能会调用GC。

 

 

 

2.使用对象池

最佳办法是程序员自己管理每一个对象的生命周期。我的理解是,自建对象池管理对象。

对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”。

 

1.对象池能避免重复创建多余对象,尤其是开销较大的对象;

2.对于一些重量级的对象,使用对象池统一管理生命周期,可以减少许多不必要的性能损耗;例如数据库连接池的使用。

 

因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。

 

3.消除过期对象引用

由于JAVA的GC会自动回收内存,确实方便了许多,但是在更多时候,对于无意识的内存泄漏情况非常多,而且很难检查。案例:

public class Stack

{

 private Object[] element;

 privat int size = 0;

 public Stack(int ca)

  {

   this.elements = new Object[ca];

  }

  public void push(Object e)

  {//这里还需要判断是否越界,略

   element[size++] = e;

  }

 public Object pop()

  {

   if ( size == 0)

    return;

     return elements[--size];

  }

}

 

    这个程序没有显然的错误,并且可以通过正常测试,但是严格的说,这个程序有一个“内存泄漏”。随着不断的使用,性能会逐渐降低,在极端情况下,会导致OOM。

我们发现,在pop()的时候,虽然实现了功能,但是出栈的对象永远不会被回收,栈内部维护着这些对象的过期引用,即使我们不在需要他它。在JDK内部,出栈的源码是这样写的:

    public synchronized Epop() {

        if (elementCount == 0){

            throw newEmptyStackException();

        }

        final int index =--elementCount;

        final E obj = (E)elementData[index];

        elementData[index] = null;

        modCount++;

        return obj;

    }

这就是一个典型的无意识泄漏。

再比如,我曾经以为回收一个Arraylist的内存是通过:Arraylist.clear(),其实源码是:

   @Override public void clear() {

       if (size != 0) {

           Arrays.fill(array,0, size, null);

           size = 0;

           modCount++;

       }

}

又是一个无意识泄漏,应该是list= null回收内存。

 

 

 

 

 

 

 

 

 

 

 

 

 

三、正确使用String对象

       privatefinal String MICRO_REG_CREATE = "CREATE TABLE IF NOT EXISTS "

                     + MicroMsgRegDao.TB_NAME +" ("

                     .....

这是QQ通讯录创建SQL表的一段代码,由于直接只用“+”运算构造字符串,我们额外创建了许多不必要的对象,在许多其他使用中,也从来不会注意这点,并试图去优化他,我们可以使用StringBilder或者StringBuffer

private final

String MICRO_REG_CREATE = newStringBuilder("CREATE TABLE IF NOT EXISTS")

                                                                                                                                            .append(MicroMsgRegDao.TB_NAME).append("(")

                                                                                                                                            .append(MicroMsgRegDao.COLUMN_ID)

                                                                                                                                            .append(" INTEGER PRIMARY KEY,")........

 

PS

String ab = "a" + "b";

相当于

String a = "a"; //第一个对象

String b = "b"; //第二个对象

String c = a+b; //第三个对象

String ab = c;

因此创建了许多不必要的对象。

为连接n个字符串而重复地使用字符串连接操作符,要求n的平方级时间

StringBuilder和StringBuffer功能完全相同,唯一不同是StringBuffer线程安全的,而StringBuilder非线程安全的。

 

 

知识拓展

StringBuffer sb = new StringBuffer()

这行代码生成一个默认StringBuffer对象,默认它能储存16个char值,当我们不停的向其中添加char时,超过16char的时候,sb会自动扩展,按照一下方法:

1.创建一个能容纳50个char的数组,并持有它;

2.将数据拷贝到新的数组中。

 

当添加的char达到51时,对象按此方式继续增长,带来了不必要的消耗。那么我们在初始化时,可以预估最终需要的字符串长度,使用

StringBuffer sb = new StringBuffer(52);

可以避免两次无效创建复制操作。

需要知道的是,Arraylist也是使用相同的机制做到动态增加,所以,如果我们可以在ArrayList初始化的时候,指定其最初长度,可以带来一定的效率增长

同时ArrayList Vector的区别在于,ArrayList线程不安全,所以高效,Vector线程安全,所以低效。结合使用场合,建议使用ArrayList

 

 

 

 

 

 

四、对常量使用Static Final修饰符

对于不需要改变的常量类型,尽可能的使用static final 声明以提高其性能。同时,对于不需要继承的Class,也可以用final修饰,可以极大的提高运行效率

 

五、在私有内部内中,考虑用包访问权限替代私有访问权限

主要是针对这种情况的写法

public class Foo {

//private改为protected可以带来效率提升

    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);

    }

}

对于内部类,在访问外部私有成员变量时,语法上是正确的,虚拟机在执行过程中会认为其非法,会生成几个综合方法来桥接这些代码,造成性能低下。

六、避免使用枚举

枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。

所以,尽量使用静态常量代替枚举变量。

 

 

 

 

 

关于Android内存泄漏

在终端设备中,由于内存有限,如果我们使用内存不当,将会照成设备运行越来越缓慢,而由于使用JAVA语言,许多童鞋忽略了内存泄漏这块,总结一下需要注意那些地方可能造成内存泄漏的。

 

 

一、造成内存泄漏的原因

由于JAVA的内存是由GC管理的,所以对程序员来说是透明的。可以通过System.gc()申请回收内存,这一接口作用是建议GC回收内存,注意,仅仅是建议回收,是否回收内存仍由GC决定,同时,建议任何时候要使用System.gc()

GC的工作原理:我们可以将对象考虑为有向图顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

很显然,Obj2会被回收,Obj1不会。

现在,给出一个案例:

 

class A

{

       private B b;

       publicA(B b)

       {

              this.b= b;

       }

}

class B

{

       privateA a;

       publicvoid setA(A a)

       {

              this.a= a;

       }

}

public static void main(String args[]) {

              Bb = new B();

              A a = new A(b);

              b.setA(a);

       }

 

这段代码演示典型JAVA内存泄漏的原理。类A引用了一个B的实例,同时这个实例又引用了A,形成闭合的环。对于这种环,即使外部已经没有引用了,但是JVM仍然无法回收内存,它认为A和B一直是存在外部引用的,内存永远得不到释放。并且,这种错误只能通过程序员review才能发现出来,非常隐蔽。

修改方法:

只需要在释放内存的时候,手动设置任意一个引用无效,切断环,即可。这里我们可以调用

b.setA(null);

 

 

 

二、释放对象的引用

Frankywang曾提到一个QQ内存泄漏的BUG,我查了一些资料,做出了记录,方便大家理解。

Android上 ,Context可以用于很多操作,但是大部分时候是用来加载以及使用资源。这就是为什么所有的widgets在他们的构造函数中接受一个Context参数。在一般的android应用中,你通常有两种Context:分别是Activity和Application。通常的,当我们的类和方法需要使用到context时,我们传递的是Activity这个context,比如:

 

private static DrawablesBackground;

@Override

protected void onCreate(Bundle state) {

 super.onCreate(state);

 

 TextView label = new TextView(this); 

  if(sBackground == null) {

    sBackground =getDrawable(R.drawable.large_bitmap);

  }

  label.setBackgroundDrawable(sBackground);

  setContentView(label);

}

 

这段代码,看起来没有问题,实际上泄漏了整个Activity。当屏幕方向改变时(横竖屏切换),系统默认销毁当前Activity并创建一个新的activity同时保持原有状态。它泄漏了第一个activity,这个在第一次屏幕改变时被创建的activity。当一个Drawable被关联到一个view上,这个view就相当于在drawable上设置的一个回调。在上面的代码片段中,注意sBackground是一个static变量。这表示丫的命很长。然而,drawable有一个TextView的引用,而这个TextView又拥有一个activity的引用(Context),activity依次引用了几乎所有的东西。于是这个Activity就这样泄漏了。

 

 

 

 

 

 

 

 

 

三、查询数据库没有关闭游标

这种情况应该比较少,非常明显的泄漏。

Cursor cursor =getContentResolver().query(uri);

if (cursor.moveToNext())

{

  ...

}

改:

Cursor cursor = null;

try

{

  cursor = getContentResolver().query(uri);

   if ( ..... )

       {}

}catch(Exception e){

} finally{

       if( cursor!= null)

       try{

          cursor.close();

       }cach(Exceptione){

}

 

四、构造Adapter时,没有使用缓存的convertView

初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同事ListView会将这些View对象缓存起来。当向下滚动ListView时,原先位于最上面的List item的View对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程是由getView()方法来完成的,getView()的第二个参数View convertView就是被缓存起来的view对象。

案例:

public View getView(int position, ViewconvertView, ViewGroup parent){

 View view = new XXView();

  ...

 return view;

}

 

 

 

 

 

 

正确写法:

public View getView(int position, ViewconvertView, ViewGroup parent){

 View view = null;

  if( convertView != null){

    view = convertView;

 }else{

    view =new XXView();

  }

  ...

 return view;

}

 

五、Bitmap对象在不使用时候调用recycle()释放内存

由于Bitmap比较占用内存,所以当它不使用的时候,建议调用Bitmap.recycle()方法回收此对象,否则,可能造成Out Of Memory异常。当然,recycle()并不是必须的,建议使用。

 

 

 

 

 

 

 

 

六、使用缓存

Frankywang交流时提到,在使用缓存的过程中,实现支持弱引用HashMap,在HashMap中的键值没有任何外部引用的时候,允许JVM回收不必要的元素,我查阅了一下QQ通讯录的源码,确实有这个实现。同时,JDK实际上已经为我们封装了这个实现,具体API是:

java.util.WeakHashMap

我们在使用缓存时,可以直接调用WeakHashMap代替HashMap:在缓存的条目过期之后,它们会自动删除。

 

 

 

 

 

 

 

七、使用单例

Frankywang交流是提到单例写法问题。单例是我们经常需要使用到的,我收集几种主流的单例写法,分析其特点。

单例一:

 public class Foo

{

  private static Foo foo = null;

  private Foo(){}

  public stataic Foo getFoo(){

     if (foo == null )

        foo = new Foo();

     returnfoo;

  }

}

分析:这是我个人最常用的写法,有安全隐患。假设线程A调用getFoo(),执行语句foo = newFoo(); 如果在初始化的过程中,另外一个线程B同样调用getFoo(),那么这个方法看到对象被部分初始化的部分,导致灾难性的后果。

 

 

 

 

 

 

单例二:

public class Foo

{

 private static Foo foo = null;

 private Foo(){}

 public stataic Foo getFoo(){

     if ( foo == null ){

        synchronized(Foo.class)

        {

          if( foo == null)

             foo = new Foo();

        }

     }

     return foo;

  }

}

分析:同样是在上种情况下,如果被共享的变量包含一个原语值,而不是一个对象引用,则它可以正常工作,否则不能正常工作。(我认为应该都可以工作,不知道原因,求解答),参见《effect java》 第二条

 

单例三:

public class Foo

{

 private static final Foo foo = new Foo();

 private Foo(){}

 public stataic Foo getFoo(){ return foo;}

}

分析:这是最佳写法,但是违反了迟缓初始化的规定,我会尽量使用这种写法。

 

单例四:

public class Foo

{

 private Foo(){}

 private static class FooHolder

  {

    static final Foo foo = new Foo();

  }

 public stataic Foo getFoo(){

     return FooHolder.foo;

  }

}

分析:最优美的写法,规避了各种风险。缺点在于,它不能用于实例域,只能用户抽象域。

 

 

Android中的性能分析

 

献上KM美文两篇,非常实用的技巧,建议大家都看看。

《Android性能分析工具之TraceView》

http://km.oa.com/group/571/articles/show/98630?kmref=search

 

《Android 应用程序内存泄漏的分析》

http://km.oa.com/group/571/articles/show/97711?kmref=search


0 0