Android 常见内存泄漏场景

如果在内存泄漏发生后再去找原因并修复会增加开发的成本,最好是在编写代码时就能够很好地考虑到内存问题,写出更高质量的代码,这里列出一些常见的内存泄漏场景,在以后的开发过程中需要避免这类问题。

1. 资源性对象未关闭

  • 资源性对象(比如 Cursor、File 文件等)往往都用了一些缓冲,在不使用时,应该及时关闭它们,以便它们的缓存数据能够及时回收。它们的缓存不只存在于 Java 虚拟机内,还存在于 Java 虚拟机外。如果仅仅是把它的引用设置为 null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如 SQLite Cursor(在析构函数 finalize()中,如果没有关闭它,它自己会调用 close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用时,应该立即调用它的 close()函数,将其关闭,然后再置为 null。在程序退出时,一定要确保资源性对象已经关闭。

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

因此,在编写资源文件读写时,都需要在 finally 中关闭资源性对象,避免在异常情况下资源对象未被释放的隐患。

2. 注册对象未注销

  • 如果事件注册后未注销,会导致观察者列表中维持着对象的引用,阻止垃圾回收,一般发生在 注册广播接收器、注册观察者等。
  • 假设在 Activity 中,监听系统中的电话服务,以获取一些信息(如信号强度等),可以在 Activity 中定义一个 PhoneStateListener 的对象,同时将它注册到 TelephonyManager 服务中。对于 Activity 对象,理论上要求 Activity 在退出后该 Activity 对象就会被释放掉。
  • 但是如果在释放 Activity 对象时,忘记取消之前注册的 PhoneStateListener 对象,则会导致 Activity 无法被 GC 回收。如果不断地进出这个 Activity,则最终会由于大量的 Activity 对象没有办法被回收而引起频繁的 GC 情况,甚至导致 Out Of Memory。

提示 虽然有些系统程序本身好像是可以自动取消注册的(不一定及时),但是
还是应该在程序中明确取消注册,程序结束时,应该取消所有的注册。

3. 类的静态变量持有大数据对象

静态变量长期维持对象的引用,阻止垃圾回收,如果静态变量持有大的数据对象,如Bitmap 等,就很容易引起内存不足等问题。

4. 非静态内部类的静态实例

非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被系统回收。如下代码是一个非静态内部类的实现。

public class MainActivity extends Activity {
    private static Text text = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (text == null) {
            text = new Text(this);
        }
    }
    
    class Text {
        private Context context;

        public Text(Context context) {
            this.context = context;
        }
    }

}

在 Activity 内部创建了一个非静态内部类的静态实例 mTestModule,每次启动 Activity时都会使用 mTestModule 的数据,这样虽然避免了资源的重复创建,但是 TestModule 为非静态内部类默认持有外部类的引用,而这里又使用该非静态内部类创建了一个静态的实例,该实例的生命周期和应用一样长,这就导致该静态实例一直持有该 Activity 的引用,Activity的内存资源不能正常回收。可以这样避免内存泄漏,即将内部类TestModule设为静态内部类或将内部类TestModule抽取出来封装成一个单例,如果需要使用 Context,就在没有特殊要求的情况下;ApplicationContext,如果需要使用 Activity Context,就记得用完后 置空让 GC 可以回收,否则还是会内存泄漏。

5. Handler临时性内存泄漏

Handler 通过发送 Message 与主线程交互是应用开发中非常常见的使用场景之一,Message 发出之后存储在 MessageQueue 中,有些 Message 也不是马上就被处理到。在Message 中存在一个 target,它是 Handler 的一个引用,Message 在 Queue 中存在的时间过长,就会导致 Handler 无法被回收。如果 Handler 是非静态的,则会导致 Activity 或者 Service不会被回收。Handler 发送 Message 与主线程交互的实现代码如下:

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }

    };
    
    private void doMessage() {
        Message message = Message.obtain();
        handler.sendMessage(message);
    }
}

由于 mHandler 是 Handler 的非静态匿名内部类的实例,所以它持有外部类 Activity 的引用,并且消息队列是在一个 Looper 线程中不断轮询处理消息,那就有一种情况,当这个Activity 退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message 持有 mHandler 实例的引用,mHandler 又持有 Activity 的引用,所以导致该 Activity的内存资源无法及时回收,引发内存泄漏。避免这种内存泄漏需要修改以下两个地方:
1)使用一个静态 Handler 内部类,然后对 Handler 持有的对象使用弱引用,这样在回收时,也可以回收 Handler 持有的对象。
2)在 Activity 的 Destroy 或者 Stop 时,应该移除消息队列中的消息,避免 Looper 线程的消息队列中有待处理的消息需要处理。

public class MainActivity extends Activity {
    private NewHandler handler = new NewHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    private static class NewHandler extends Handler {
        private WeakReference<Context> mContextWeakReference = null;

        public NewHandler(Context context) {
            mContextWeakReference = new WeakReference<Context>(context);
        }


        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

    private void doMessage() {
        Message message = Message.obtain();
        handler.sendMessage(message);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}

6. 容器中的对象没清理造成内存泄漏

通常把一些对象的引用加入集合中,在不需要该对象时,如果没有把它的引用从集合中清理掉,这个集合就会越来越大。如果这个集合是 static,情况就更严重。

7. WebView

Android 中的 WebView 不仅仅存在很大的兼容性问题,不同 Android
系统版本中的 WebView 会有较大的差异,加上不同厂商定制的 ROM 中的 WebView 也存在着差异。更严重的是 WebView 都存在内存泄漏的问题,在应用中只要使用一次 Webview,内存就不会被释放掉。通常解决这个问题的办法是为 WebView 开启独立的一个进程,使用AIDL 与应用的主进程进行通信,WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/100559270
今日推荐