android Handler导致的内存泄露

栏目: 后端 · 发布时间: 5年前

内容简介:那么,该从上述示例代码可知:上述的引用关系会一直保持,直到

1. 问题描述

  • Handler 的一般用法 = 新建 Handler 子类(内部类) 、匿名 Handler 内部类
/** 
     * 方式1:新建Handler子类(内部类)
     */  
    public class MainActivity extends AppCompatActivity {

            public static final String TAG = "carson:";
            private Handler showhandler;

            // 主线程创建时便自动创建Looper & 对应的MessageQueue
            // 之后执行Loop()进入消息循环
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);

                //1. 实例化自定义的Handler类对象->>分析1
                //注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
                showhandler = new FHandler();

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

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

            }

            // 分析1:自定义Handler子类
            class FHandler extends Handler {

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


                    }
                }
            }
        }

   /** 
     * 方式2:匿名Handler内部类
     */ 
     public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        private Handler showhandler;

        // 主线程创建时便自动创建Looper & 对应的MessageQueue
        // 之后执行Loop()进入消息循环
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

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

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

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

        }
}
复制代码
  • 测试结果

    android Handler导致的内存泄露

    示意图

  • 上述例子虽可运行成功,但代码会出现严重警告:
  1. 警告的原因 = 该 Handler 类由于无设置为 静态类,从而 导致了内存泄露
  2. 最终的内存泄露发生在 Handler 类的外部类: MainActivity
android Handler导致的内存泄露

示意图

那么,该 Handler 在无设置为静态类时,为什么会造成内存泄露呢?

2. 原因讲解

2.1 储备知识

  • 主线程的 Looper 对象的生命周期 = 该应用程序的生命周期
  • Java 中, 非静态内部类 & 匿名内部类 都默认持有 外部类的引用

2.2 泄露原因描述

从上述示例代码可知:

  • 上述的 Handler 实例的消息队列有2个分别来自线程1、2的消息(分别 为延迟 1s6s
  • Handler 消息队列 还有未处理的消息 / 正在处理消息时,消息队列中的 Message 持有 Handler 实例的引用
  • 由于 Handler = 非静态内部类 / 匿名内部类(2种使用方式),故又默认持有外部类的引用(即 MainActivity 实例),引用关系如下图

上述的引用关系会一直保持,直到 Handler 消息队列中的所有消息被处理完毕

android Handler导致的内存泄露

示意图

  • Handler 消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类 MainActivity ,但由于上述引用关系,垃圾回收器 (GC) 无法回收 MainActivity ,从而造成内存泄漏。如下图:
android Handler导致的内存泄露

示意图

2.3 总结

  • Handler 消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler 实例 -> 外部类”
  • 若出现 Handler 的生命周期 > 外部类的生命周期 时( Handler 消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时 ),将使得外部类无法被垃圾回收器 (GC) 回收,从而造成 内存泄露

3. 解决方案

从上面可看出,造成内存泄露的原因有2个关键条件:

Handler
Handler

Handler 消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁

解决方案的思路 = 使得上述任1条件不成立 即可。

解决方案1:静态内部类+弱引用

  • 原理

    静态内部类 不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler 实例 -> 外部类” 的引用关系 的引用关系 不复存在。

  • 具体方案

    Handler 的子类设置成 静态内部类

  • 同时,还可加上 使用WeakReference弱引用持有Activity实例
  • 原因:弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
  • 解决代码
public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";
    private Handler showhandler;

    // 主线程创建时便自动创建Looper & 对应的MessageQueue
    // 之后执行Loop()进入消息循环
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //1. 实例化自定义的Handler类对象->>分析1
        //注:
            // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
            // b. 定义时需传入持有的Activity实例(弱引用)
        showhandler = new FHandler(this);

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

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

    }

    // 分析1:自定义Handler子类
    // 设置为:静态内部类
    private static class FHandler extends Handler{

        // 定义 弱引用实例
        private WeakReference<Activity> reference;

        // 在构造方法中传入需持有的Activity实例
        public FHandler(Activity activity) {
            // 使用WeakReference弱引用持有Activity实例
            reference = new WeakReference<Activity>(activity); }

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


            }
        }
    }
}
复制代码

解决方案2:当外部类结束生命周期时,清空Handler内消息队列

  • 原理

    不仅使得 “未被处理 / 正处理的消息 -> Handler 实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler 的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步

  • 具体方案

    当 外部类(此处以 Activity 为例) 结束生命周期时(此时系统会调用 onDestroy() ),清除 Handler 消息队列里的所有消息(调用 removeCallbacksAndMessages(null)

  • 具体代码

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

使用建议

为了保证 Handler 中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式

4. 总结

  • 本文主要讲解了 Handler 造成 内存泄露的相关知识:原理 & 解决方案
  • 实际中我们使用rxjava更方便.

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Solve It

How to Solve It

Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95

This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具