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

//子线程Handler
Handler 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)。

ps:关于方法一,我似乎有听过有人说最好不要通过这种方式来处理Handler的内存泄漏问题,可是我忘了具体原因是什么又或是我记错了?如果大家有知道的请在评论区大方赐教一番...



参考了以下文献:

深入源码解析Android中的Handler,Message,MessageQueue,Looper

Android 多线程之HandlerThread 完全详解》

Handler内存泄漏详解及其解决方案》

《Android开发艺术探索》


你可能感兴趣的:(面试)