Android内存使用注意事项

      在 Android 开发中经常会由于不正确的使用资源,造成内存泄漏的问题。在此总结了一些常见的

造成内存泄漏的情况,希望大家在开发过程中注意。

(一) 查询数据库没有关闭游标
描述:
       程序中经常会进行查询数据库的操作,但是经常会有使用完毕 Cursor 后没有关闭的情况。如果我
们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,
这样就会给以后的测试和问题排查带来困难和风险。
示例代码:

复制内容到剪贴板代码:    Cursor cursor = getContentResolver().query(uri ...);
    if (cursor.moveToNext()) {... ...}

修正示例代码:

复制内容到剪贴板代码:    Cursor cursor = null;
    try {
        cursor = getContentResolver().query(uri ...);
        if (cursor != null && cursor.moveToNext()) {... ...}
    } finally {
        if (cursor != null) {
            try {
                cursor.close();
            } catch (Exception e) {
                //ignore this
            }
        }
    }

(二) 构造 Adapter 时,没有使用缓存的 convertView
描述:
       以构造 ListView 的 BaseAdapter 为例,在 BaseAdapter 中提高了方法:
public View getView(int position, View convertView, ViewGroup parent)
来向 ListView 提供每一个 item 所需要的 view 对象。初始时 ListView 会从 BaseAdapt
er 中根据当前的屏幕布局实例化一定数量的 view 对象,同时 ListView 会将这些 view 对象缓存起
来 。当向上滚动 ListView 时,原先位于最上面的 list item 的 view 对象会被回收,然后被用来构造
新出现的最下面的 list item。这个构造过程就是由 getView()方法完成的,getView()的第二个形
参 View convertView 就是被缓存起来的 list item 的 view 对象(初始化时缓存中没有 view 对
象则 convertView 是 null)。
       由此可以看出,如果我们不去使用 convertView,而是每次都在 getView()中重新实例化一个 Vi
ew 对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView 回收 list item 的 vie
w 对象的过程可以查看一下方法 :
android.widget.AbsListView.java --> void addScrapView(View scrap)
示例代码:

复制内容到剪贴板代码:    public View getView(int position, View convertView, ViewGroup parent) {
        View view = new Xxx(...);... ...return view;
    }

修正示例代码:

复制内容到剪贴板代码:    public View getView(int position, View convertView, ViewGroup parent) {
        View view = null;
        if (convertView != null) {
            view = convertView;
            populate(view, getItem(position));
            ...
        } else {
            view = new Xxx(...);
            ...
        }
        return view;
    }

(三) Bitmap 对象不在使用时调用 recycle()释放内存
描述:
       有时我们会手工的操作 Bitmap 对象,如果一个 Bitmap 对象比较占内存,当它不在被使用的时候,
可以调用 Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的 ,视情况而定。可
以看一下代码中的注释:
/**
* Free up the memory associated with this bitmap's pixels, and mark the
* bitmap as "dead", meaning it will throw an exception if getPixels() or
* setPixels() is called, and will draw nothing. This operation cannot be
* reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/

(四) 释放对象的引用
描述:
       这种情况描述起来比较麻烦,举两个例子进行说明。
示例 A:
假设有如下操作

复制内容到剪贴板代码:    public class DemoActivity extends Activity {
        ... ...
        private Handler mHandler = ...
        private Object obj;
        public void operation() {
            obj = initObj();
            ...
            [Mark]
            mHandler.post(new Runnable() {
                public void run() {
                    useObj(obj);
                }
            });
        }
    }

我们有一个成员变量 obj,在 operation()中我们希望能够将处理 obj 实例的操作 post 到某个线
程的 MessageQueue 中。在以上的代码中,即便是 mHandler 所在的线程使用完了 obj 所引用的
对象,但这个对象仍然不会被垃圾回收掉,因为 DemoActivity.obj 还保有这个对象的引用。所以如果
在 DemoActivity 中不再使用这个对象了,可以在[Mark]的位置释放对象的引用,而代码可以修改为:

复制内容到剪贴板代码:    ... ...
    public void operation() {
        obj = initObj();
        ...
        final Object o = obj;
        obj = null;
        mHandler.post(new Runnable() {
            public void run() {
                useObj(o);
            }
        }
    }
    ... ...

示例 B:
      假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以
在 LockScreen 中定义一个 PhoneStateListener 的对象,同时将它注册到TelephonyManager 服务中。
      对于 LockScreen 对象,当需要显示锁屏界面的时候就会创建一个 LockScreen 对象,而当锁屏界面消失的
时候 LockScreen 对象就会被释放掉。但是如果在释放 LockScreen 对象的时候忘记取消我们之前注册的 
PhoneStateListener 对象,则会导致 LockScreen 无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终
会由于大量的 LockScreen 对象没有办法被回收而引起 OutOfMemory,使得 system_process 进程挂掉。
       总之当一个生命周期较短的对象 A,被一个生命周期较长的对象 B 保有其引用的情况下,在 A 的生命
周期结束时,要在 B 中清除掉对 A 的引用。

(五)合理配置图片资源
      一个应用的资源目录下的图片资源通常会存放在以下几个目录中:
drawable、drawable-hdpi、drawable-mdpi、drawable-ldpi
      合理的图片资源放置方法为:
/res/drawable : 通用的图片、XML 文件(如<selector>)
/res/drawable-hdpi : 仅存放高分辨率的图片(如 WVGA (480x800),FWVGA (480x854))
/res/drawable-mdpi : 仅存放中等分辨率的图片(如 HVGA (320x480))
/res/drawable-ldpi : 仅存放低分辨率的图片(QVGA (240x320))
       系统会根据设备的分辨率自动到相应的文件夹下获取图片。
如果不这样放置会造成不必要的内存浪费,甚至最后导致内存泄漏。例如,如果把本来应该放在 hdpi 中图片放
在了 mdpi 中,每次在使用该图片时系统都会重新加载该图片,而不会使用之前已有的缓存。

(六)注意 static 成员变量的使用
       有时为了节省内存或是提高效率,我们会将一些资源写成 static 的形式,以避免重新加载,但是一定要非常
注意这种资源的回收。例如一个 static 的 ImageView,它本身保有 Context 的引用,如果没有合理的释放该 
ImageView,则会造成相应的 Context (Activity)的内存不会被回收。

(七)合理配置 Acitivity 的 launchMode 属性
      例如,应该是 singleTop 的,就不要是 standard 的,尽量节省内存。

(八) 其他
      Android 应用程序中最典型的需要注意释放资源的情况是在 Activity 的生命周期中, 在onPause()、onStop()、
onDestroy()方法中需要适当的释放资源的情况。由于此情况很基础,在此不详细说明,具体可以查看官方文档对 
Activity 生命周期的介绍,以明确何时应该释放哪些资源。

(九) 网上资料
      已经有越来越多的Android开发人员总结了他们对内存使用方面的宝贵经验,网上就有很多这样的资料,以下
列出了几篇blog,其内容也涉及到了一些本文没有提到的情况。有几篇blog以前已经发过多次了,不过在此还是
统一列出来。

我的blog(入门):
[Android] 内存泄漏调试经验分享 (一)
http://rayleeya.javaeye.com/blog/727074
[Android] 内存泄漏调试经验分享 (二)
http://rayleeya.javaeye.com/blog/755657

另外 @Rosen的blogjava上的文章对内存泄漏的分析更为深 入(进阶)
使用Memory Analyzer tool(MAT)分析内存泄漏(一)
http://www.blogjava.net/rosen/archive/2010/05/21/321575.html
使用Memory Analyzer tool(MAT)分析内存泄漏(二)
http://www.blogjava.net/rosen/archive/2010/06/13/323522.html

Android,谁动了我的内存(1)
http://winuxxan.blog.51cto.com/2779763/512179
Android,谁动了我的内存(2)
http://winuxxan.blog.51cto.com/2779763/512180

Android application context/activity context与内存泄露
http://hi.baidu.com/455611934/blog/item/ffa9fbcf34c4931592457ec2.html

提醒大家一点,有些blog中的说法并不一定完全正确,希望大家在工作中结合实际情况得出自己的观点。

posted on 2012-02-03 19:19  Fleezn  阅读(268)  评论(0编辑  收藏  举报

导航