`

内存泄漏调试经验分享

阅读更多

( 查询数据库没有关闭游标

描述:

    程序中经常会进行查询数据库的操作,但是经常会有使用完毕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 会从 BaseAdapter 中根据当前的屏幕布局实例化一定数量的 view 对象,同时 ListView 会将这些 view 对象缓存起来。当向上滚动 ListView 时,原先位于最上面的 list item view 对象会被回收,然后被用来构造新出现的最下面的 list item 。这个构造过程就是由 getView() 方法完成的, getView() 的第二个形参  View convertView 就是被缓存起来的 list item view 对象 ( 初始化时缓存中没有 view 对象则 convertView null)

    由此可以看出,如果我们不去使用convertView ,而是每次都在 getView() 中重新实例化一个 View 对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。 ListView 回收 list item view 对象的过程可以查看 :

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 的引用。

( 其他

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

_Android_内存泄漏调试.pdf,以下有下载:

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics