Android常见内存泄漏的原因

415 阅读4分钟

单例模式使用Activity作为Context

单例模式中应该避免使用Activity作为传入的Context,否则,单例模式会持有这个Activity的引用,导致它无法释放,造成内存泄漏。应该使用ApplicationContext作为Context传入。如果一定要使用Activity的话,要使用弱引用WeakReference

未关闭资源或者没有反注册

BroadcastReceiver,File,Cursor,IO流等资源在 Activity 的onDestroy必须unregister 或者 close ,否则这个 Activity 类会被 system 强引用,不会被内存回收。关闭的语句必须在finally中进行关闭,否则有可能因为异常未关闭资源,致使activity泄漏。

Handler造成内存泄漏

只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。特别是handler执行延迟任务。所以,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //do something
        }
    };
    private void loadData(){
        //do request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
}

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private void loadData() {
        //do request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<Context>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = (MainActivity) reference.get();
            if (mainActivity != null) {
                //do something to update UI via mainActivity
            }
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
}

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,

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

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

小结:

  • 非静态内存类有持有对外部类的引用,所以改为使用静态内部类

  • 对Activity的引用改为弱引用,使得Activity可以被顺利回收释放

  • Activity在Destroy的时候应该移除消息队列中的消息

  • 同理,AsyncTask造成内存泄漏的原理和解决办法也是相同的思路,具体的解释请看我之前的这篇博客:

    Android中的强引用、软引用、弱引用和虚引用你真的都懂了嘛?

线程

线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的旧的 Activity 也不会被销毁,因此就出现了内存泄露的问题。

Bitmap 使用完没有注意 recycle()回收

Bitmap 作为大对象,在使用完毕一定要注意调用 recycle() 进行回收。TypedArrayCursor、各种流同理,一定要在最后调用自己的回收关闭方法处理。

集合类没有删除机制

集合类如果只有添加机制,没有删除机制,导致内存被占用。特别这个集合是全局的变量,比如类中的静态变量。

总结:

  • 内存泄漏的根本原因是一个长生命周期的对象持有了一个短生命周期的对象。
  • 可以采用譬如 LeakCanary 这样的库去做检测

参考:

www.jianshu.com/p/b0345cb39…

www.wanandroid.com/wenda/show/…

juejin.cn/post/684490…