美文网首页AndroidAndroid内存溢出 内存泄露
Android 内存泄露:详解 Handler 内存泄露的原因

Android 内存泄露:详解 Handler 内存泄露的原因

作者: Carson带你学安卓 | 来源:发表于2016-09-23 10:16 被阅读3801次

    前言

    • Android开发中,内存泄露 十分常见
    1. 内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中
    2. 内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。
      这就导致了内存泄漏。
    • 本文将详细讲解内存泄露的其中一种情况:在Handler中发生的内存泄露

    阅读本文前,建议先阅读文章:Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)


    目录

    示意图

    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();
    
            }
    }
    
    • 测试结果


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

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


    2. 原因讲解

    2.1 储备知识

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

    2.2 泄露原因描述

    从上述示例代码可知:

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

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

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

    2.3 总结

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

    3. 解决方案

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

    1. 存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
    2. 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 造成 内存泄露的相关知识:原理 & 解决方案
    • 接下来,我会继续讲解 Android开发中关于内存泄露的知识,有兴趣可以继续关注Carson_Ho的安卓开发笔记

    请点赞!因为你的鼓励是我写作的最大动力!

    相关文章阅读
    Android开发:最全面、最易懂的Android屏幕适配解决方案
    Android事件分发机制详解:史上最全面、最易懂
    Android开发:史上最全的Android消息推送解决方案
    Android开发:最全面、最易懂的Webview详解
    Android开发:JSON简介及最全面解析方法!
    Android四大组件:Service服务史上最全面解析
    Android四大组件:BroadcastReceiver史上最全面解析


    欢迎关注Carson_Ho的简书!

    不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度

    相关文章

      网友评论

      • Solang:解决方案1中倒数几行的reference 赋值了却没有使用,你不觉得有问题?弱引用光摆着没意义吧
      • 洛的俠:受教了!
      • zl_adams:你好,当handler被修饰成静态后,handleMessage方法里面如果要调用外部的成员变量是需要把调用的成员变量也修饰成静态的,例如要刷新页面的控件,这样的话,用静态的handler无法实现吧
      • 3866b3f2e166:大神,图中提示Handler那里提示语"this hanlder should be static or leak will occur"是什么插件显示的吗?
        下位子:@月影唯殇 as 自带
      • 相互交流:楼主想问你一个问题 ,Handler造成内存泄漏,是因为activity 中有子线程正在请求数据,但是这个时候activity 销毁 子线程还在做出来引用了Handler对象造成了引用了activity对象不能被销毁,不能被回收(我的理解是在这次GC不能被回收),但是子线程做完任务,不在引用 activity,难道下一次发生GC回收 还不能回收?希望楼主给予回答,再次感谢
        Boerma:内存泄露并不是致命错误,所以你说的在下次回收也是有可能的。上面的警告是在编译期间提醒你有泄漏的风险。写代码就是要考虑有几率出现的问题,加以优化。
        总之随便啦:子线程结束了 不代表handler结束了 handler一般是绑定在主线程的 我是这么认为的
      • 面熟_gson:哥,有时间讲讲handler原理机制呗,带源码的哪种,你的文章都写的很清楚
        面熟_gson:@Carson_Ho 特喜欢看你的博客,写的比较容易看懂,get到不少新知识,会一直关注你,
        Carson带你学安卓:@面熟_3fd5 早写啦,请看https://www.jianshu.com/p/9fe944ee02f7
      • 7a79714f3eb1:解决方案1:使用静态内部类+弱引用的例子是不是写错了?既然添加了新的构造函数,为啥在实例化的时候private final Handler showhandler = new FHandler()还是无参的呢?
        smartapple:@Enter侠 传了this的呢
        36f872af9773:嗯,我也不太明白
      • navy_legend:讲的不错,感谢分享
      • 迟迟Zss:写的很好,我大概看懂了😙

      本文标题:Android 内存泄露:详解 Handler 内存泄露的原因

      本文链接:https://www.haomeiwen.com/subject/revzettx.html