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更方便.

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

查看所有标签

猜你喜欢:

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

自制编程语言 基于C语言

自制编程语言 基于C语言

郑钢 / 人民邮电出版社 / 2018-9-1 / CNY 89.00

本书是一本专门介绍自制编程语言的图书,书中深入浅出地讲述了如何开发一门编程语言,以及运行这门编程语言的虚拟机。本书主要内容包括:脚本语言的功能、词法分析器、类、对象、原生方法、自上而下算符优先、语法分析、语义分析、虚拟机、内建类、垃圾回收、命令行及调试等技术。 本书适合程序员阅读,也适合对编程语言原理感兴趣的计算机从业人员学习。一起来看看 《自制编程语言 基于C语言》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器