Android 内存泄露:详解 Handler 内存泄露的原因与解决方案

浪人 等级 313 0 0

前言

  • 在Android开发中,内存泄露 十分常见

1.内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中
2.内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。这就导致了内存泄漏。

  • 本文将详细讲解内存泄露的其中一种情况:在Handler中发生的内存泄露

目录

Android 内存泄露:详解 Handler 内存泄露的原因与解决方案

1.png

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();

        }
} 
  • 测试结果
![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/86bc74dfd73dd32b1c5649ec401b475c.png)

1.png
  • 上述例子虽可运行成功,但代码会出现严重警告:

1.警告的原因 = 该Handler类由于无设置为 静态类,从而导致了内存泄露
2.最终的内存泄露发生在Handler类的外部类:MainActivity类

Android 内存泄露:详解 Handler 内存泄露的原因与解决方案

1.png

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

2. 原因讲解

2.1 储备知识

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

2.2 泄露原因描述

从上述示例代码可知:

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

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

Android 内存泄露:详解 Handler 内存泄露的原因与解决方案

1.png

  • 在Handler消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。如下图:
![](https://img-hello-world.oss-cn-beijing.aliyuncs.com/041c65f92dc2d1cfb615410a899cb91a.png)

1.png

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解决内存泄露问题,即 静态内部类 + 弱引用的方式

收藏
评论区

相关推荐

Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础。下面我们分析一下Handler的源码实现。 Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message Handler : 获取消息,发送消息,以及处理消息的类 Mes
Android HandlerThread源码解析
在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环。那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时的任务或者写日志的功能。这就是HandlerThread的作用 Android Handler消息机制源码解析(https://www.cnblogs.co
4.1 手写Java PriorityQueue 核心源码
本章先讲解优先级队列和二叉堆的结构。下一篇代码实现 从一个需求开始 假设有这样一个需求:在一个子线程中,不停的从一个队列中取出一个任务,执行这个任务,直到这个任务处理完毕,再取出下一个任务,再执行。 其实和 Android 的 Handler 机制中的 Looper 不停的从 MessageQueue 中取出一个消息然后处理是一样的。 不过这个需
vue的watch监听
vue的watch监听函数 watch用来响应数据的变化,watch 的用法大致有以下三种: 1. 监听某个变量 watch: { name(newName, oldName) { console.log(newName) // 改变前的值 console.log(oldName) // 改变后的值
还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar
或许对于许多Android开发者来说,所谓的Android工程师的工作“不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上”就好了,源码什么的,看也好不看也罢,反正应用层的开发用不上,再加上现在优秀的轮子越来越多,拿来主义泛滥,能用就是,反正老板也不关心是不是你自己写的,用我现在老大的话来说,阅读源码似乎只是一种“锦上添花”
Android高频面试题:该怎样在Android面试中聊聊多线程不被忽悠?
多线程可以说是Android面试的高频问题了,而多线程涉及的内容非常多,因此在面试当中往往不知道从何说起,本文并不是为了科普多线程或者研究多线程的知识,而是尝试组织语言
Android开发你必须了解的几个原理
随着互联网的迅速发展,Android技术也是发生很大的变化,要求也是越来高了,在11,12年只要会基本的Android组件,会listview,分享就感觉很牛了,智能手机的发展,及用户普通追求高效率,用户体验的提升,要求开发人员必须会懂实现原理及优化APP程序;不管是面试他人还是被面试目前都经常问到原理性的问题,handler实现原理,activity启动原
Android开发 常见异常和解决办法(一)
Android Studio是Android开发的理想工具,但是由于版本的更新和配置的差异,会出现很多问题,下面是以《第一行代码 第二版》为基础进行开发学习可能遇见的一些问题及其解决办法。 1.Android Studio 3.0及以上版本找不到Android Device Monitor: 解决办法: (1)在Android Studio中打开终端,如下
Android 内存泄露:详解 Handler 内存泄露的原因与解决方案
前言 在Android开发中,内存泄露 十分常见 1.内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中 2.内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。这就导致了内存泄漏。 本文将详细讲解内存泄露的其中一种情况:在Handler中发生的内
2021 春招通关 BAT 最新 Android 常用面试真题
前言金三银四招聘旺季已经开启,如果有面试准备的,临阵磨枪,不快也光。如果没面试想法的,也可以看看这些问题以及它们的解析,对提升技术也是很有帮助的。内容涵盖五大部分:Java基础部分、Android部分、数据结构与算法部分、常用的开源库部分、计算机网络认识部分。 Java基础部分内容主要包括操作系统相关、 JDK&JVM&JRE、面向
Gin + Vue全栈开发实战
      尝试地写了第一篇自己学习Go Web框架的感受和入门的文章,发现反响还不错,大家也提出了很多的问题来一起交流。近期也渐渐地出现了很多有关go语言开发的相关文章,包括有在蚂蚁金服的大牛的分享,我也一直有在看博客园和学习,这里越来越多人的去学习
Android 操作系统架构开篇
版权声明: 本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容。为便于日常查阅本博客,可通过 方便检索文章 一、引言众所周知,Android是谷歌开发的一款基于Linux的开源操作系统,从诞生至今已有10余年,这一路走来Android遇到哪些问题?大版本升级朝着什么方向演进?Android的未来如何?我的公号 讲解了Android一
Android深入理解JNI(一)JNI原理与静态、动态注册
Android框架层 Android深入理解JNI Android框架层本文首发于微信公众号「刘望舒」 前言JNI不仅仅在NDK开发中应用,它更是Android系统中Java与Native交互的桥梁,不理解JNI的话,你就只能停留在Java Framework层。这一个系列我们来一起深入学习JNI。<!more 1.JNI概述Android系统按语言来划分的
Android解析WindowManagerService(一)WMS的诞生
Android框架层 Android系统服务 WindowManagerService Android框架层本文首发于微信公众号「后厂技术官」 前言此前我用多篇文章介绍了WindowManager,这个系列我们来介绍WindowManager的管理者WMS,首先我们先来学习WMS是如何产生的。本文源码基于Android 8.0,与Android 7.1.2
每日一问(一)Handler相关知识
1、Handler负责发送Message和处理Mesage2、Message就是消息载体,可用what区分,也可传递对象3、Message Queue消息队列,存储Message,先进先出4、Looper循环取出Message Queue里的Message交给Handler处理。5、一个线程只有一个Looper和Message Queue,子线程中使用Han