Android性能优化之Handler内存溢出

内存溢出的定义

本来应该被回收的对象不能被回收而停留在堆内存中。

内存溢出的原因

当一个对象实例不再被使用时,正常来说应该被回收,但却因为有另外一个正在使用的对象持有它的引用,从而导致它不能被回收。这就导致了内存溢出。

由Handler引起的内存溢出问题

Handler的一般用法是在Activity中新建Handler子类(内部类)或匿名Handler内部类。
我们分析下面这个例子:


import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
 * 方式1:新建Handler子类(内部类)
 */
public class MainActivity extends AppCompatActivity {


    // 主线程创建时便自动创建Looper和对应的MessageQueue
    // 之后执行Loop()进入消息循环

    class MyHandler extends Handler {
        // 通过重写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    Log.d("MyHandler", "收到线程1的消息");
                    break;
                case 2:
                    Log.d("MyHandler", " 收到线程2的消息");
                    break;
            }
        }
    }

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

        // 1. 实例化自定义的Handler类对象
        // 此处不需要指定Looper,因为在主线程中创建Handler已自动绑定当前线程(主线程)的Looper、MessageQueue
        myHandler = new MyHandler();
        // 2. 启动子线程1
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "Tea";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();
        // 3. 启动子线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);// 睡5秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2;// 消息标识
                msg.obj = "Water";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();
    }
}

上面是通过非静态内部类的方式来使用Handler,下面我们看看以匿名内部类的方式:


import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
 * 方式2:匿名内部类
 */
public class MainActivity extends AppCompatActivity {
    // 主线程创建时便自动创建Looper和对应的MessageQueue
    // 之后执行Loop()进入消息循环
    private Handler myHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1. 实例化自定义的Handler类对象
        // 此处不需要指定Looper,因为在主线程中创建Handler已自动绑定当前线程(主线程)的Looper、MessageQueue
        myHandler = new Handler() {
            // 通过复写handlerMessage() 从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.d("MyHandler", "收到线程1的消息");
                        break;
                    case 2:
                        Log.d("MyHandler", " 收到线程2的消息");
                        break;
                }
            }
        };

        // 2. 启动子线程1
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "Tea";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

        // 3. 启动子线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2;// 消息标识
                msg.obj = "Water";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

    }

}

使用匿名内部类的方式IDE更是直接地给出了提示:
意思是说Handler应该设置为static否则可能会发生溢出。
Android性能优化之Handler内存溢出_第1张图片
上面两个例子里的Handler都可以直接使用外部类里的任何变量和方法,为什么呢?因为在Java中,非静态内部类和匿名内部类都默认持有外部类的引用

主线程的Looper循环器对象的生命周期 = 该应用程序的生命周期

Handler异步消息机制中有3个重要的类, 它们的关系如下:
Android性能优化之Handler内存溢出_第2张图片

  • Handler对象绑定着一个MessageQueue实例,就是Handler对象里持有一个MessageQueue的实例,Handler发送的消息就是装入这个消息队列对象里的。
  • Looper循环器持有MessageQueue的引用。
  • Message消息对象持有Handler对象的引用,所以Looper循环器取出一个消息对象时,就能通过消息对象Message持有的Handler对象,调用Handler对象的dispatchMessage方法,dispatchMessage方法里会调用Handler对象的handleMessage方法。

溢出原因

当Handler消息队列还有未处理的消息或正在处理消息时,消息队列中的Message消息对象持有Handler实例的引用,Handler又持有外部类的引用(即MainActivity实例)。此时若销毁外部类MainActivity,那是不可能成功的。因为MainActivity在需要销毁时还被引用着,所以垃圾回收器(GC)无法回收MainActivity,从而造成内存溢出。

Handler造成内存溢出的原因总结:

  • 当Handler消息队列还有未处理的消息或正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”,导致外部类需销毁时无法销毁,从而造成内存溢出。

解决方案

(1)静态内部类+弱引用
为了保证Handler中消息队列中的所有消息都能被执行,推荐此方式来解决内存溢出问题:
静态内部类+弱引用:

 private static class MyHandler extends Handler{
        // 定义弱引用实例
        private WeakReference<MainActivity> mainActivityWeakReference;
        public MyHandler(MainActivity activity){
            // 使用WeakReference弱引用持有Activity实例
            this.mainActivityWeakReference = new WeakReference<>(activity);
        }
        // 通过重写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d("MyHandler", "收到线程1的消息");
                    break;
                case 2:
                    Log.d("MyHandler", " 收到线程2的消息");
                    break;
            }
        }
    }

完整代码如下:

package com.ti.controlsound;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.lang.ref.WeakReference;

/**
 * 方式1:新建Handler子类(内部类)
 */
public class MainActivity extends AppCompatActivity {
    
    // 主线程创建时便自动创建Looper和对应的MessageQueue
    // 之后执行Loop()进入消息循环
    private MyHandler myHandler;

    private static class MyHandler extends Handler{
        // 定义弱引用实例
        private WeakReference<MainActivity> mainActivityWeakReference;
        public MyHandler(MainActivity activity){
            // 使用WeakReference弱引用持有Activity实例
            this.mainActivityWeakReference = new WeakReference<>(activity);
        }
        // 通过重写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Log.d("MyHandler", "收到线程1的消息");
                    break;
                case 2:
                    Log.d("MyHandler", " 收到线程2的消息");
                    break;
            }
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 实例化自定义的Handler类对象->>分析1
        // 注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
        myHandler = new MyHandler(this);
        // 2. 启动子线程1
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "Tea";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

        // 3. 启动子线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2;// 消息标识
                msg.obj = "Water";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

    }

}

(2)当外部类结束生命周期时,清空Handler内消息队列
改进后的代码:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 外部类Activity生命周期结束时,同时清空消息队列 和 结束Handler生命周期
        myHandler.removeCallbacksAndMessages(null);
    }

完成代码如下:

package com.ti.controlsound;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
 * 方式1:新建Handler子类(内部类)
 */
public class MainActivity extends AppCompatActivity {


    // 主线程创建时便自动创建Looper和对应的MessageQueue
    // 之后执行Loop()进入消息循环


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

        // 1. 实例化自定义的Handler类对象->>分析1
        // 注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
        myHandler = new Handler() {
            // 通过复写handlerMessage() 从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.d("MyHandler", "收到线程1的消息");
                        break;
                    case 2:
                        Log.d("MyHandler", " 收到线程2的消息");
                        break;
                }
            }
        };

        // 2. 启动子线程1
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1;// 消息标识
                msg.obj = "Tea";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

        // 3. 启动子线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2;// 消息标识
                msg.obj = "Water";// 消息存放
                // 传入主线程的Handler,利用myHandler向其MessageQueue发送消息
                myHandler.sendMessage(msg);
            }
        }.start();

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 外部类Activity生命周期结束时,同时清空消息队列 和 结束Handler生命周期
        myHandler.removeCallbacksAndMessages(null);
    }
}

谢谢阅读。

你可能感兴趣的:(Android开发,Handler,内存溢出)