现在的位置: 首页 > 综合 > 正文

Android内存优化

2012年09月11日 ⁄ 综合 ⁄ 共 9587字 ⁄ 字号 评论关闭

文章转自:http://blog.csdn.net/arui319/article/details/8549849

 

通常我们写程序,都是在项目计划的压力下完成的,此时完成的代码可以完成具体业务逻辑,但是性能不一定是最优化的。一般来说,优秀的程序员在写完代码之后都会不断的对代码进行重构。重构的好处有很多,其中一点,就是对代码进行优化,提高软件的性能。下面我们就从几个方面来了解Android开发过程中的代码优化。

1)静态变量引起内存泄露

在代码优化的过程中,我们需要对代码中的静态变量特别留意。静态变量是类相关的变量,它的生命周期是从这个类被声明,到这个类彻底被垃圾回收器回收才会被销毁。所以,一般情况下,静态变量从所在的类被使用开始就要一直占用着内存空间,直到程序退出。如果不注意,静态变量引用了占用大量内存的资源,造成垃圾回收器无法对内存进行回收,就可能造成内存的浪费。

先来看一段代码,这段代码定义了一个Activity。

private static Resources mResources; 

@Override

protected void onCreate(Bundle state) {

super.onCreate(state);

if (mResources == null) {

    mResources = this.getResources();

    }

}

这段代码中有一个静态的Resources对象。代码片段mResources = this.getResources()对Resources对象进行了初始化。这时Resources对象拥有了当前Activity对象的引用,Activity又引用了整个页面中所有的对象。

如果当前的Activity被重新创建(比如横竖屏切换,默认情况下整个Activity会被重新创建),由于Resources引用了第一次创建的Activity,就会导致第一次创建的Activity不能被垃圾回收器回收,从而导致第一次创建的Activity中的所有对象都不能被回收。这个时候,一部分内存就浪费掉了。

经验分享:

在实际项目中,我们经常会把一些对象的引用加入到集合中,如果这个集合是静态的话,就需要特别注意了。当不需要某对象时,务必及时把它的引用从集合中清理掉。或者可以为集合提供一种更新策略,及时更新整个集合,这样可以保证集合的大小不超过某值,避免内存空间的浪费。

    2)使用Application的Context

在Android中,Application Context的生命周期和应用的生命周期一样长,而不是取决于某个Activity的生命周期。如果想保持一个长期生命的对象,并且这个对象需要一个Context,就可以使用Application对象。可以通过调用Context.getApplicationContext()方法或者Activity.getApplication()方法来获得Application对象。

依然拿上面的代码作为例子。可以将代码修改成下面的样子。

private static Resources mResources; 

@Override

protected void onCreate(Bundle state) {

super.onCreate(state);

if (mResources == null) {

    // mResources = this.getResources();

    mResources = this.getApplication().getResources();

    }

}

    在这里将this.getResources()修改为this.getApplication().getResources()。修改以后,Resources对象拥有的是Application对象的引用。如果Activity被重新创建,第一次创建的Activity就可以被回收了。

3)及时关闭资源

Cursor是Android查询数据后得到的一个管理数据集合的类。正常情况下,如果我们没有关闭它,系统会在回收它时进行关闭,但是这样的效率特别低。如果查询得到的数据量较小时还好,如果Cursor的数据量非常大,特别是如果里面有Blob信息时,就可能出现内存问题。所以一定要及时关闭Cursor。

下面给出一个通用的使用Cursor的代码片段。

Cursor cursor = null;

try{

    cursor = mContext.getContentResolver().query(uri,null,null,null,null);

    if (cursor != null) {

        cursor.moveToFirst();

        // 处理数据

    }

} catch (Exception e){

    e.printStatckTrace();

} finally {

    if (cursor != null){

        cursor.close();

    }

}

即对异常进行捕获,并且在finally中将cursor关闭。

同样的,在使用文件的时候,也要及时关闭。

4)使用Bitmap及时调用recycle()

前面的章节讲过,在不使用Bitmap对象时,需要调用recycle()释放内存,然后将它设置为null。虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。

在代码优化的过程中,如果发现某个Activity用到了Bitmap对象,却没有显式的调用recycle()释放内存,则需要分析代码逻辑,增加相关代码,在不再使用Bitmap以后调用recycle()释放内存。

    5)对Adapter进行优化

下面以构造ListView的BaseAdapter为例说明如何对Adapter进行优化。

在BaseAdapter类中提供了如下方法:

public View getView(int position, View convertView, ViewGroup parent)

当ListView列表里的每一项显示时,都会调用Adapter的getView方法返回一个View,

来向ListView提供所需要的View对象。

下面是一个完整的getView()方法的代码示例。

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

  ViewHolder holder;

if (convertView == null) {

      convertView = mInflater.inflate(R.layout.list_item, null);

      holder = new ViewHolder();

      holder.text = (TextView) convertView.findViewById(R.id.text);

      convertView.setTag(holder);

  } else {

      holder = (ViewHolder) convertView.getTag();

  }

  holder.text.setText("line" + position);

  return convertView;

}

private class ViewHolder {

  TextView text;

}

当向上滚动ListView时,getView()方法会被反复调用。getView()的第二个参数convertView是被缓存起来的List条目中的View对象。当ListView滑动的时候,getView可能会直接返回旧的convertView。这里使用了convertView和ViewHolder,可以充分利用缓存,避免反复创建View对象和TextView对象。

如果ListView的条目只有几个,这种技巧并不能带来多少性能的提升。但是如果条目有几百甚至几千个,使用这种技巧只会创建几个convertView和ViewHolder(取决于当前界面能够显示的条目数),性能的差别就非常非常大了。

   

    6)代码“微优化”

    当今时代已经进入了“微时代”。这里的“微优化”指的是代码层面的细节优化,即不改动代码整体结构,不改变程序原有的逻辑。尽管Android使用的是Dalvik虚拟机,但是传统的Java方面的代码优化技巧在Android开发中也都是适用的。

下面简要列举一部分。因为一般Java开发者都能够理解,就不再做具体的代码说明。

创建新的对象都需要额外的内存空间,要尽量减少创建新的对象。

将类、变量、方法等等的可见性修改为最小。

针对字符串的拼接,使用StringBuffer替代String。

不要在循环当中声明临时变量,不要在循环中捕获异常。

如果对于线程安全没有要求,尽量使用线程不安全的集合对象。

使用集合对象,如果事先知道其大小,则可以在构造方法中设置初始大小。

文件读取操作需要使用缓存类,及时关闭文件。

慎用异常,使用异常会导致性能降低。

如果程序会频繁创建线程,则可以考虑使用线程池。

经验分享:

代码的微优化有很多很多东西可以讲,小到一个变量的声明,大到一段算法。尤其在代码Review的过程中,可能会反复审查代码是否可以优化。不过我认为,代码的微优化是非常耗费时间的,没有必要从头到尾将所有代码都优化一遍。开发者应该根据具体的业务逻辑去专门针对某部分代码做优化。比如应用中可能有一些方法会被反复调用,那么这部分代码就值得专门做优化。其它的代码,需要开发者在写代码过程中去注意。

 

使用软引用和弱引用

 

Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

这里重点介绍一下软引用和弱引用。

如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。

在java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

下面以使用软引用为例来详细说明。弱引用的使用方式与软引用是类似的。

假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。

首先定义一个HashMap,保存软引用对象。

 

private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

再来定义一个方法,保存Bitmap的软引用到HashMap。

 

    public void addBitmapToCache(String path) {

        // 强引用的Bitmap对象

        Bitmap bitmap = BitmapFactory.decodeFile(path);

        // 软引用的Bitmap对象

        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

        // 添加该对象到Map中使其缓存

        imageCache.put(path, softBitmap);

    }

    获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。

 

    public Bitmap getBitmapByPath(String path) {

        // 从缓存中取软引用的Bitmap对象

        SoftReference<Bitmap> softBitmap = imageCache.get(path);

        // 判断是否存在软引用

        if (softBitmap == null) {

            return null;

        }

        // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空

        Bitmap bitmap = softBitmap.get();

        return bitmap;

    }

使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。

 

经验分享:

到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。

 

UI优化

在Android应用开发过程中,屏幕上控件的布局代码和程序的逻辑代码通常是分开的。界面的布局代码是放在一个独立的xml文件中的,这个文件里面是树型组织的,控制着页面的布局。通常,在这个页面中会用到很多控件,控件会用到很多的资源。Android系统本身有很多的资源,包括各种各样的字符串、图片、动画、样式和布局等等,这些都可以在应用程序中直接使用。这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安装包的大小。

下面从几个方面来介绍如何利用系统资源。

1)利用系统定义的id

比如我们有一个定义ListView的xml文件,一般的,我们会写类似下面的代码片段。

 

<ListView

    android:id="@+id/mylist"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"/>

这里我们定义了一个ListView,定义它的id是"@+id/mylist"。实际上,如果没有特别的需求,就可以利用系统定义的id,类似下面的样子。

 

<ListView

    android:id="@android:id/list"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"/>

在xml文件中引用系统的id,只需要加上“@android:”前缀即可。如果是在Java代码中使用系统资源,和使用自己的资源基本上是一样的。不同的是,需要使用android.R类来使用系统的资源,而不是使用应用程序指定的R类。这里如果要获取ListView可以使用android.R.id.list来获取。

2)利用系统的图片资源

    假设我们在应用程序中定义了一个menu,xml文件如下。

 

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item

        android:id="@+id/menu_attachment"

        android:title="附件"

        android:icon="@android:drawable/ic_menu_attachment" />

</menu>

其中代码片段android:icon="@android:drawable/ic_menu_attachment"本来是想引用系统中已有的Menu里的“附件”的图标。但是在Build工程以后,发现出现了错误。提示信息如下:

 

error: Error: Resource is not public. (at 'icon' with value '@android:drawable/ic_menu_attachment').

从错误的提示信息大概可以看出,由于该资源没有被公开,所以无法在我们的应用中直接引用。既然这样的话,我们就可以在Android SDK中找到相应的图片资源,直接拷贝到我们的工程目录中,然后使用类似android:icon="@drawable/ic_menu_attachment"的代码片段进行引用。

这样做的好处,一个是美工不需要重复的做一份已有的图片了,可以节约不少工时;另一个是能保证我们的应用程序的风格与系统一致。

 

经验分享:

Android中没有公开的资源,在xml中直接引用会报错。除了去找到对应资源并拷贝到我们自己的应用目录下使用以外,我们还可以将引用“@android”改成“@*android”解决。比如上面引用的附件图标,可以修改成下面的代码。

android:icon="@*android:drawable/ic_menu_attachment"

修改后,再次Build工程,就不会报错了。

3)利用系统的字符串资源

    假设我们要实现一个Dialog,Dialog上面有“确定”和“取消”按钮。就可以使用下面的代码直接使用Android系统自带的字符串。

 

    <LinearLayout

        android:orientation="horizontal"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content">

        <Button

            android:id="@+id/yes"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_weight="1.0"

            android:text="@android:string/yes"/>

        <Button

            android:id="@+id/no"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_weight="1.0"

            android:text="@android:string/no"/>

    </LinearLayout>

如果使用系统的字符串,默认就已经支持多语言环境了。如上述代码,直接使用了@android:string/yes和@android:string/no,在简体中文环境下会显示“确定”和“取消”,在英文环境下会显示“OK”和“Cancel”。

    4)利用系统的Style

    假设布局文件中有一个TextView,用来显示窗口的标题,使用中等大小字体。可以使用下面的代码片段来定义TextView的Style。

 

    <TextView

        android:id="@+id/title"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceMedium" />

    其中android:textAppearance="?android:attr/textAppearanceMedium"就是使用系统的style。需要注意的是,使用系统的style,需要在想要使用的资源前面加“?android:”作为前缀,而不是“@android:”。

5)利用系统的颜色定义

除了上述的各种系统资源以外,还可以使用系统定义好的颜色。在项目中最常用的,就是透明色的使用。代码片段如下。

 

android:background ="@android:color/transparent"

 

经验分享:

Android系统本身有很多资源在应用中都可以直接使用,具体的,可以进入android-sdk的相应文件夹中去查看。例如:可以进入$android-sdk$\platforms\android-8\data\res,里面的系统资源就一览无余了。

开发者需要花一些时间去熟悉这些资源,特别是图片资源和各种Style资源,这样在开发过程中,能够想到有相关资源并且直接拿来使用。

---------------------------------------------------------------------------

http://blog.csdn.net/arui319

《Android应用开发精解》已出版,欢迎购买阅读。

本文可以转载,但是请保留以上作者信息。

谢谢。

---------------------------------------------------------------------------

抱歉!评论已关闭.