Handler为什么可能会造成内存泄漏以及可用的四种解决方法

摘要:
在Android系统中,Handler是消息发送和处理机制的核心组件之一,支持它的其他主要组件有Looper、message和MessageQueue。上述翻译意味着Handler主要用作消息发送和接收的机制。Message和Runnable类是消息的载体。这意味着处理程序的实例与单个线程及其MessageQueue相关联。为什么处理程序可能导致内存泄漏。因此,处理程序实例保存对SampleActivity的引用。处理器就是一个典型的例子。以上面的代码为例。如果使用第二个方法,则当需要在处理程序内部调用外部类的非静态方法时,它无法满足要求。

在Android系统中,Handler是一个消息发送和处理机制的核心组件之一,与之配套的其他主要组件还有Looper和Message,MessageQueue。

根据官网的描述

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler有两个主要作用:

1.安排调度(scheule)消息和可执行的runnable,可以立即执行,也可以安排在某个将来的时间点执行。

2.让某一个行为(action)在其他线程中执行。

上面翻译的意思也就是Handler主要作为一种消息收发的机制。

这个消息可以是单纯的基本类型,也可以是某个类,或者一个可执行的行为(runnable)。Message和Runnable类是消息的载体。MessageQueue是消息等待的队列。Looper则负责从队列中取消息。

在官网描述中,有一段描述很重要:Each Handler instance is associated with a single thread and that thread's message queue。意思是一个Handler的实例和单个的线程和这个线程的MessageQueue相关联。这得出了一个结论:如果这个MessageQueue中的消息是有某个Handler的instance(实例)的引用的。

关于这一点,其实不难理解:Looper处理消息Message类的时候,需要调用Handler的handleMessage吧,这就需要知道是哪个Handler的实例,才能调用Handler.handleMessge()

现在回到题目的问题上。Handler为什么可能造成内存泄漏。这里的内存泄漏,常常指的是泄漏了Activity等组件。可能引起泄漏的操作是这种格式的代码:

public class TestActivity extends Activity{

    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

注意这是上面的代码是有问题的代码,但不一定会引起内存泄漏,只是有可能,泄漏对象的是SampleActivity实例。

这有什么问题呢。问题在于该Handler的实例采用了内部类的写法,它是SampleActivity这个实例的内部类,在Java中,关于内部类有一个特点:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。所以,该handler实例持有了SampleActivity的一个引用。到这里,是不是有点头绪了呢。

关于内存泄漏,在android中一个通用的说法是:生命周期较短的组件引用了生命周期较长的组件。Handler就是一种典型的示例,以上面的代码举例。SampleActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。比如发送了一个延时消息到队列中,那么就可能在队列中存在很长时间,而消息队列(MessageQueue)的生命周期等于它所在的线程。当大到Activity被finish()了后还在队列中时,就满足了上面的短生命周期引用长生命周期的条件。根据Java GC的规则,SampleActivity的引用计数不为0,故不会回收,回收的时机在handler发送的消息出队列时。

从上面的说法中,可以思考得到相应的解决方法:

1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。

2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)

3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)

4.将Handler放到一个单独的顶层类文件中。

最好的方法是哪一种呢?其实前三种方法都差不多,第四种如果是一些轻量的操作就太多余了。不过要说通用性,第三种是最为通用的。

如果用第一种,其具体的解决方法是当组件销毁时,在恰当的时机调用handler的removeCallbacksAndMessages(null),如果是在Activity中,则是在onDestroy()的生命周期回调中调用。如果是Activity等具有明确生命周期的组件时可以这么做,但要是在自定义的类中,比如一个单例中,往往不能找好释放的时机。而且开发人员有时会忘记调用remove消息的方法。

如果用第二种,当在handler内部需要调用外部类的非静态方法时就达不到要求了。因为在Java中,静态的内部类中不能调用外部非静态的方法。

第三种,需要一些额外的代码,但方法最为通用。

 public class TestActivity extends Activity {

    private static class MyHandler extends Handler {
    private final WeakReference<TestActivity> mActivity;
    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<TestActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      TestActivity activity = mActivity.get();
      if (activity != null) {
         //do Something
      }
    }
 }

采用哪种方法其实都是可以的,具体看实际情况。

参考资料:

https://developer.android.com/reference/android/os/Handler 官方Reference。

https://blog.csdn.net/lqw_student/article/details/52954837

免责声明:文章转载自《Handler为什么可能会造成内存泄漏以及可用的四种解决方法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇msfvenome木马生成的实战及其文档(kali-01)Linux安装loadrunner负载机【转】下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MFC的简单加法器(二)

   创建对话框主要分两大步,第一,创建对话框资源,主要包括创建新的对话框模板、设置对话框属性和为对话框添加各种控件;第二,生成对话框类,主要包括新建对话框类、添加控件变量和控件的消息处理函数等。鸡啄米在本节中先讲讲怎样创建对话框模板和设置对话框属性。        创建基于对话框的应用程序框架        之前鸡啄米创建的HelloWorld程序是单文...

人工智能-有限状态机(FSM)的学习

首先声明:此文源于本人最近学习的一本书 《游戏人工智能编程案例精粹》 FSM的定义: 一个有限状态机是一个设备,或是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得从一个状态变换到另一个状态,或者是促使一个输出或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。 FSM的实现: 不要用if else语句或者switc...

【Android】Android中线程的应用

1. Android进程    在了解Android线程之前得先了解一下Android的进程。当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。同时,Android会为每个应用程序分配一个单独的LINUX用户。Android会尽量保留一个正在运行进程,只在内存资源出现不足...

在HTTP通讯过程中,是客户端还是服务端主动断开连接?

比如说:IE访问IIS,获取文件,肯定是要建立一个连接,这个连接在完成通讯后,是客户端Close了连接,还是服务端Close了连接。我用程序测模拟IE和IIS,都没有收到断开连接的消息,也就是都没有触发OnClose事件。我是用Socket建立的连接。如果两方面都没有主动断开连接,那么我猜测可能是传输的数据中有结束的标志,请问这个标志是怎样的?谢谢各位。...

RocketMQ 4.x 介绍以及安装

Apache RocketMQ 是阿里开源的一款高性能、高吞吐量的分布式消息中间件. 官网:http://rocketmq.apache.org/ 特点 支持 Broker 和 Consumer 端消息过滤 支持发布订阅模型,和点对点, 支持拉 pull 和推 push 两种消息模式 单一队列百万消息、亿级消息堆积 支持单 master 节点,多 mas...

TWinControl的消息覆盖函数大全(41个WM_函数和31个CM_函数,它的WndProc就处理鼠标(转发)、键盘(取消拖动)、焦点、和WM_NCHITTEST一共4类消息)

注意,这些函数只有Private一种形式(也就是不允许覆盖,但仍在动态表格中): 其中TWinControl对TControl有10个消息进行了覆盖(红色标记),其中有2个是WM_消息,8个是CM_消息。 TWinControl = class(TControl) private //41个windows消息,几乎全部消息都是私有函数(...