Android消息机制及HandlerThread、Handler内存泄漏问题
第一部分:Android消息机制讲解。
Android的消息机制主要指的是Handler的运行机制。Handler的运行主要靠底层的MessageQueue和Looper支持。
一、MessageQueue
消息队列,其内部存储了一组消息,以队列的形式对外提供插入、删除操作。但其内部数据结构并不是队列,而是单链表。
队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢? 因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue里面去,即将单击按钮事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。MessageQueue中有两个比较重要的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中阻塞式地取出一个Message。在Android中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的所有窗口。需要注意的是,消息队列不是Android平台特有的,其他的平台框架也会用到消息队列,比如微软的MFC框架等。
MessageQueue插入一条消息就是对单链表进行插入操作,主要通过 enqueueMessage方法实现,而这个方法内部实现主要就是对单链表的插入操作。
MessageQueue取出一条消息是通过 next方法实现的,取出一条消息时,还会将这条消息删除。next方法是一个无限循环方法,当有消息来时,next方法会返回这条消息并将其删除。如果队列里没有消息,那么next方法会一直阻塞。
二、Looper
循环,MessageQueue只是一个消息存储单元,真正让消息队列循环起来的是Looper。
默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列MessageQueue和Looper的。为了能够让线程能够绑定一个消息队列,我们需要借助于Looper:首先我们要调用Looper的prepare方法,然后调用Looper的Loop方法。
Looper的构造函数,我们看一下其代码:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}
Looper的构造函数是private的,也就是在该类的外部不能用new Looper()的形式得到一个Looper对象。线程Thread和Looper是一对一绑定的,也就是一个线程中最多只有一个Looper对象,这也就能解释Looper的构造函数为什么是private的了,我们只能通过工厂方法Looper.myLooper()这个静态方法获取当前线程所绑定的Looper。
在其构造函数中实例化一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。
myLooper的代码如下所示:
public static Looper myLooper() { return sThreadLocal.get();}
sThreadLocal是什么?
ThreadLocal是Thread内部的数据存储类,通过它可以在指定的线程中存储数据,并且只有在指定的线程才能获取该数据。
它存储了该线程的looper。通过get、set方法存取looper。
现在看一下Looper.prepare(),该方法是让Looper做好准备,只有Looper准备好了之后才能调用Looper.loop()方法,Looper.prepare()的代码如下:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}
上面的代码首先通过sThreadLocal.get()拿到线程sThreadLocal所绑定的Looper对象,由于初始情况下sThreadLocal并没有绑定Looper,所以第一次调用prepare方法时,sThreadLocal.get()返回null,不会抛出异常。重点是下面的代码sThreadLocal.set(new Looper(quitAllowed)),首先通过私有的构造函数创建了一个Looper对象的实例,然后通过sThreadLocal的set方法将该Looper绑定到sThreadLocal中。
这样就完成了线程sThreadLocal与Looper的双向绑定:
a. 在Looper内通过sThreadLocal可以获取Looper所绑定的线程;
b.线程sThreadLocal通过sThreadLocal.get()方法可以获取该线程所绑定的Looper对象。
在执行完了Looper.prepare()之后,我们就可以在外部通过调用Looper.myLooper()获取当前线程绑定的Looper对象。 这时候我们就可以调用Looper.loop()方法让消息队列循环起来了。
Looper.loop()是个静态方法,代码我就不贴出来了,我总结了里面的关键点:
1. final MessageQueue queue = me.mQueue;
变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper,me.mQueue是当前线程所关联的消息队列。2. for (;;)
我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。 唯一跳出循环的标志是:MessageQueue的next方法返回null。当Looper的quit方法(退出方法)被调用时,Looper会调用MessageQueue的quit方法通知其退出,当MessageQueue要退出时,它的next方法返回null。
3. Message msg = queue.next(); // might block
我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,如果此时消息队列中有Message,那么next方法会立即返回该Message,如果此时消息队列中没有Message,那么next方法就会阻塞式地等待获取Message。
4. msg.target.dispatchMessage(msg);
当收到新的消息时,Looper会处理它:msg.target.dispatchMessage(msg) 。msg.target是发送这条消息的Handler对象。dispatchMessage方法是在创建Handler时所使用的Looper中执行的。
三、Handler
Handler的工作主要就是发送、处理消息。
先来说说他的构造方法:
Handler具有多个构造函数,分别如下所示:
1. public Handler()
2. public Handler(Callbackcallback)
3. public Handler(Looperlooper)
4. public Handler(Looperlooper, Callbackcallback)
第1个和第2个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。
第3个和第4个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。
也就是说,在Handler的构造方法中就完成了和当前线程的Looper的绑定操作。
第2个和第4个构造函数还传递了Callback对象,Callback是Handler中的内部接口,需要实现其内部的handleMessage方法,Callback代码如下:public interface Callback { public boolean handleMessage(Message msg);}
Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法: 1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法
Handler的发送过程:
主要是一系列的postXXX方法和sendMessageXXX方法发送消息,其最终都调用了sendMessageAtTime方法发送消息。如果使用postXXX方法发送消息,那么Handler会将post内的Runable对象赋值给Message的callback对象,然后转换为message对象,进行传递。
于是来看看sendMessageAtTime方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //注意下面这行代码 return enqueueMessage(queue, msg, uptimeMillis);}
该方法内部调用了enqueueMessage方法,该方法的源码如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //注意下面这行代码 msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //注意下面这行代码 return queue.enqueueMessage(msg, uptimeMillis);}
在该方法中有两件事需要注意:
1. msg.target = this
该代码将Message的target绑定为当前的Handler 。 msg.target有木有很熟悉?在Looper.loop方法中,Looper通过MessageQueue的next方法获取到Message后,就通过msg.target.dispatchMessage(msg)处理消息。到这就很简单理解这个方法了:让该msg所绑定的Handler(msg.target)去执行dispatchMessage方法处理消息。
2. queue.enqueueMessage
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。
Handler的处理过程:
通过dispatchMessage处理消息:
public void dispatchMessage(Message msg) { //注意下面这行代码 if (msg.callback != null) { handleCallback(msg); } else { //注意下面这行代码 if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //注意下面这行代码 handleMessage(msg); }}
我们来分析下这段代码:
1.首先会判断msg.callback存不存在,msg.callback是Runnable类型,如果msg.callback存在,那么说明该Message是通过执行Handler的postXXX系列方法将Message放入到消息队列中的,这种情况下会执行handleCallback(msg), handleCallback源码如下:
private static void handleCallback(Message message) { message.callback.run();}
这样我们我们就清楚地看到我们执行了msg.callback的run方法,也就是执行了postXXX所传递的Runnable对象的run方法。
2.如果我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下执行,接着我们会判断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,我们在上面提到过,在Handler的构造函数中我们可以传递Hanlder.Callback类型的对象,该对象需要实现handleMessage方法,如果我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。
3.如果我们在构造函数中没有传入Callback类型的对象,那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们需要自己是重写实现该方法。
综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。
第二部分:HandlerThread、Handler相关的内存泄漏问题。
一、HandlerThread:
HandlerThread继承自Thread,它的实现也很简单:在run()方法内通过Looper.prepare来创建消息队列,并通过Looper.loop来开启消息循环,这样就允许在HandlerThread中创建Handler了。创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
我们先来看看HandlerThread使用步骤。
1.创建实例对象
1. HandlerThread handlerThread = new HandlerThread("downloadImage");
传入参数的作用主要是标记当前线程的名字,可以任意字符串。
2.启动HandlerThread线程
1. //必须先开启线程 2. handlerThread.start();
到此,我们创建完HandlerThread并启动了线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤:
3.构建循环消息处理机制
/** * 该callback运行于子线程 */ class ChildCallback implements Handler.Callback { @Override public boolean handleMessage(Message msg) { //在子线程中进行相应的网络请求 //通知主线程去更新UI mUIHandler.sendMessage(msg1); return false; } }
4.构建异步handler
//子线程HandlerHandler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
第3步和第4步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象以及Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成
再看看HandlerThread的源码(70行,非常简单!):
public class HandlerThread extends Thread { int mPriority; int mTid = -1; Looper mLooper; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } //也可以指定线程的优先级,注意使用的是 android.os.Process 而不是 java.lang.Thread 的优先级! public HandlerThread(String name, int priority) { super(name); mPriority = priority; } // 子类需要重写的方法,在这里做一些执行前的初始化工作 protected void onLooperPrepared() { } //获取当前线程的 Looper //如果线程不是正常运行的就返回 null //如果线程启动后,Looper 还没创建,就 wait() 等待 创建 Looper 后 notify public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) { //循环等待 try { wait(); } catch (InterruptedException e) { } } } return mLooper; } //调用 start() 后就会执行的 run() @Override public void run() { mTid = Process.myTid(); Looper.prepare(); //帮我们创建了 Looepr synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); //Looper 已经创建,唤醒阻塞在获取 Looper 的线程 } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); //开始循环 mTid = -1; } public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false; } public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false; } public int getThreadId() { return mTid; }}
是不是很简单?
主要需要注意两个方法:run 和 getLooper
run:
前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。
getLooper:
在哪用到了这个方法呢? 在上面的使用步骤的第四步。用于创建子线程Handler时传入当前线程Looper。run方法内只是准备好了Looper,允许在子线程创建Handler,最后我们还是要自己去创建Handler的。可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。
HandlerThread的主要用途在实现IntentService,后面会专门写一篇文章介绍IntentService。
二、使用Handler时的内存泄漏问题:
在进行异步操作时,我们经常会使用到Handler类。最常见的写法如下。
但是,这段代码有可能会引起内存泄漏。我们来分析一下内存泄漏是如何发生的:
当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)。一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。
解决方法:
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。
参考了以下文献:
《深入源码解析Android中的Handler,Message,MessageQueue,Looper》
《Android 多线程之HandlerThread 完全详解》
《Handler内存泄漏详解及其解决方案》
《Android开发艺术探索》
更多相关文章
- Android(安卓)双缓冲技术
- 换一种方式理解 Android协程
- android之Handler整理
- Android事件传递机制(更加深入的了解事件的触发过程)
- Handler原理
- android HTTP 通信, XML 解析, 通过 Hander 实现异步消息处理 (1)
- Android开发之Android开发规范(初)
- android消息推送方案
- 面试题目及其答案