记一次 由多线程引起的内存泄漏导致的OOM问题

项目是去年七月份写的,功能都完成了。因为时间比较紧,就没怎么测试。后来想把这个项目用来做毕业设计,在测试的时候出现了问题。点击注销登录,跳转到登录页面,重新登录,跳转到主页,然后出现OOM,怀疑出现了内存泄漏。

主要原因是,注销登录后(从主界面跳转到登录页面),某个类持有MainActivity的实例,导致MainActivity 无法得到回收,导致内存泄漏。

于是集成了 Leakcanary进行检测,刚开始Leakcanary显示是这样的
记一次 由多线程引起的内存泄漏导致的OOM问题

Leakcanary 显示 EaseConversationList 持有 MainActivity 的实例,导致无法进行回收。

简单,将EaseConversationList 类里面的所有 context,换成context.getApplicationContext()

然后又显示了这个错误
记一次 由多线程引起的内存泄漏导致的OOM问题

Leakcanary 显示 EaseConversationList 的父容器 LinearLayout持有MainActivity的引用,确实是这样的

看一下源码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hyphenate="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/common_bg"
    android:orientation="vertical">

	<com.hyphenate.easeui.widget.EaseTitleBar 
	    android:id="@+id/title_bar"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    hyphenate:titleBarTitle="@string/session"
	    />
    
    <include layout="@layout/ease_search_bar" />
    
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/fl_error_item"
        >
    </FrameLayout>


    <com.hyphenate.easeui.widget.EaseConversationList
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:cacheColorHint="#00000000"
        android:divider="@null"
        hyphenate:cvsListPrimaryTextSize="16sp"
         />

</LinearLayout>

试着解决一下 ,重写布局,不能让布局持有 MainActivity的实例


public class MyLayout extends LinearLayout {
    public MyLayout(Context context) {
        this(context.getApplicationContext(),null);
    }

    public MyLayout(Context context, @Nullable AttributeSet attrs) {
        this(context.getApplicationContext(), attrs,0);
    }

    public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context.getApplicationContext(), attrs, defStyleAttr);
    }


}

修改布局:

<?xml version="1.0" encoding="utf-8"?>
<com.wsg.xsybbs.view.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hyphenate="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/common_bg"
    android:orientation="vertical">

	<com.hyphenate.easeui.widget.EaseTitleBar 
	    android:id="@+id/title_bar"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    hyphenate:titleBarTitle="@string/session"
	    />
    
    <include layout="@layout/ease_search_bar" />
    
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/fl_error_item"
        >
    </FrameLayout>


    <com.hyphenate.easeui.widget.EaseConversationList
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:cacheColorHint="#00000000"
        android:divider="@null"
        hyphenate:cvsListPrimaryTextSize="16sp"
         />

</com.wsg.xsybbs.view.MyLayout>

本以为解决了,但是又出现下一个问题

记一次 由多线程引起的内存泄漏导致的OOM问题
记一次 由多线程引起的内存泄漏导致的OOM问题

显示MyMessageFragment 存在匿名类 实现runable方法
看一下代码

    private void initData() {
        // run in a second
        final long timeInterval = 10000;
        Runnable runnable = new Runnable() {
            public void run() {
                while (true) {
                    // ------- code for task to run
                    conversationListView.refresh();
                    // ------- ends here
                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        new Thread(runnable).start();
    }
    

这下子终于找到了真正的原因,这个线程如果没有关闭的话,会一直运行下去。
当注销登录时,调用了MainActivity的finish()方法,但是这个线程没有被关闭, conversationListView.refresh();一直都在被调用。

conversationListView 一直持有MainActivity的引用,导致MainActivity 无法被回收,引起内存泄漏,最终出现OOM。

好了,问题发现了,接下来 就是 在注销登录的时候关闭这个线程就好啦。

1、线程直接关闭 使用共享变量和 interrupt();。 不知为何无法进行关闭线程

2、使用线程池,关闭线程池,进而关闭线程。 不知为何也无法进行关闭线程。shutdown()、shutdownNow() 都不行

3、既然前两种方式不行,那就使用系统回调进行关闭,借助 AsyncTask 解决成功。Leakcanary 再也没有显示内存泄漏

   private MyTask myTask;


   private void initData() {
        myTask=new MyTask();
        myTask.execute();
    }


    class MyTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... voids) {

            // run in a second
            final long timeInterval = 10000;
            while (true) {
                // ------- code for task to run
                conversationListView.refresh();
                // ------- ends here
                try {
                    Thread.sleep(timeInterval);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
    }


    @Override
    public void onDestroy() {

        //如果异步任务不为空 并且状态是 运行时  ,就把他取消这个加载任务
        if(myTask !=null && myTask.getStatus() != AsyncTask.Status.FINISHED){
            myTask.cancel(true);

        }
        super.onDestroy();
    }


然后就再也没有出现内存泄漏问题

在多线程开发中,如果一个线程可能会一直运行下去,必须进行显试的关闭。以后需要多注意。

不过最好的话 还是在应该 在应用 全局 封装一个线程池吧