Android Handler的消息机制,简单易懂

684 阅读10分钟

前言:俗话说的好,万丈高楼平地起,学好数理化,走遍天下都不怕;只有先打好基础的情况下,才可以更深入地去学习知识;今天带大家设计一下Handler的机制,会通过一种特别的的方式,来进行讲解,希望可以对你有所帮助;

Handler的疑惑:

1,首先提出一个疑问,为什么面试官都很爱问Handler相关的知识?因为Handler机制是Android一个非常重要的通信机制,很多框架的底层实现都是通过Handler来更新UI的;

2,那么问题来了,Android在哪里使用Handler消息机制,为什么要使用这个Handler?下面我们一步步的来深入了解,揭开Handler的真面目吧!

Handler的前生:

1,在Android的世界,存在着无数的线程,其中有一个特殊的线程, 被赋予了特殊的使命,它就是传说中的主线程;为什么说它特殊呢?因为它掌握着可以更新UI(视图)的权力;那么问题来了,其它的线程说我也要更新UI呢?总不能也给这个线程赋予更新UI的权力吧,如果每个线程都去更新UI,很容易就乱套了,那怎么解决呢?很简单,交由主线程去更新就行了;那么怎么通知主线程去更新呢?请继续往下看;

2,Google工程师造了主线程之后,相应的也创造了一些工具给主线程使用,是什么工具呢?就是类似通讯工具的Handler,没错,Handler就通讯工具,至于通讯工具用来做什么,很简单,就是用来发送消息️;当其它的线程需要更新UI的时候,只需要打个电话给主线程,把你要传递的消息(Message)告诉它(主线程)就行了;️

3,通讯工具是怎么运作的呢?当通讯工具发送消息的时候,会把消息传送到最近的通信基站(MessageQueue),这时候还有一个角色出场了,就是通信卫星(Looper),通信卫星是一个无时无刻不在工作的辛勤劳动者,通信卫星(Looper)的作用就是从通信基站(MessageQueue)里面取出消息,然后发送给主线程的通讯工具,这时候主线程家里的(callBack)接受到其它线程发来的消息后,更新UI;

当然上面纯属个人举例,用于加深理解!

下面我们来看看Handler的设计!

Handler机制的设计:

1,在开始之前

假如你Google工程师,你面临着一个难题,多个线程在一起工作,大家都有更新UI的需求,时不时的你更新一下UI,我再更新一下UI,这时候有可能会导致更新的东西被别的线程给覆盖了,这就是多个线程同时操作会出现的问题;那这个问题要怎么解决呢?

2,更新UI的需求

既然大家都有更新UI的需求,那为何不统一管理,使用一个线程来更新UI即可,其它线程需要更新UI的时候,告诉那个可以更新UI的线程,让它来更新就行,这样就可以避免上面那种情况的出现;既然有了思路,那接下来要怎么实现呢?请继续往下看!

3,划分线程界限

先规定一个线程为主线程,赋予它可以更新UI的权力,然后再设计一个可以发送消息的通讯工具,将其命名为Handler,Handler的职责就是发送消息,通知可以更新UI的线程,我们需要更新UI了;那么设计出来的效果如下,里面有一个可以发送消息的方法enqueueMessage();

public class Handler {
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

现在有了发送消息的工具,接下来还得设计另一个工具用来接收消息;

4,MessageQueue和Message的设计

在设计之前,有一个疑惑,如果有多个线程同时要操作更新UI的话,那肯定是没办法同时操作;比如说:有好几个人要去售票处买火车票,但是售票处只有一个窗口,没办法做到同时给那么多人售票,这时候就可以让人们进行排队,有序的购票,这样大家都能买到票,又不会出现混乱的情况;这里我们也可以采用这种方法来解决问题;设计一个消息队列MessageQueue,让Handler发送的消息,在这里进行排队,设计如下:

public final class MessageQueue {
    Message mMessages
    // 存储消息的方法
    boolean enqueueMessage(Message msg, long when) {

        synchronized (this) {
            Message p = mMessages;
            
            if (p == null) {
                // 将第一个消息添加进队列
                msg.next = p;
                mMessages = msg;
            } else {
            Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                }
                // 将传进来的消息链接到上一个消息的后面,进行排队
                prev.next = msg;
            }
        }
        return true;
    }
}

消息队列(MessageQueue)里面主要设计了一个链表的结构,让进来的消息可以链接到上一个消息的后面,从而达到排队的目的,也就是enqueueMessage()方法;

再看一下消息的本体结构设计,设计了一个排队的标记next,标记谁排在它的后面,用于链接队列:

public final class Message implements Parcelable {
    public int what;
    ...
    Message next;
}

4,Looper的设计

现在有了发送消息的Handler,也有了接收消息的队列(MessageQueue),那么还需要一个可以把消息从消息队列里面取出来的工具;

在设计之前,有一个疑问:线程有可能会发送消息,有可能不会发送消息,我并不知道消息队列里面是否有消息;

由此得知,这个工具需要不停的查看消息队列里面有没有消息,有的话就将其取出来,避免耽误了其它线程更新UI的需求,这就要求这个工具需要时刻不停的工作着,那么就将其设计为不停工作的轮循器(Looper);

轮循器里面设计了一个无限循环的机制,可以不停的从消息队列里面取出消息,那么设计出来的效果如下:

public final class Looper {
    public static void loop() {
        ...
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        // 不停的循环从队列里面取出消息来;
        for (;;) {
            ...
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                // 回调给主线程
                msg.target.dispatchMessage(msg);
            } finally {
                ...
            }
            ...
        }
    }
}

轮循器Looper里面有一个无限循环的方法,可以一直从消息队列(MessageQueue)里面取出消息出来;

这时候又有疑问了,轮循器(Looper)取出来的消息要怎么发给主线程?

我们可以通过设计一个回调,来将这个消息回调给主线程;

接下来在Handler里面设计一个接口,可以将Looper发送的消息回调到主线程,通过handleMessage()方法,这个方法最终是通过Handler里的dispatchMessage来进行回调;

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}
    
/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        ...
    }
}

Handler里的dispatchMessage()则设计由Looper来进行调用;

5,ThreadLocal的设计

那么到这里一个完整的消息机制就设计完了,但是这样子就结束了吗?然而事情并没有这么简单,且听我细细道来!

上面设计的只是给主线程使用的一套消息机制,一个轮循器(Looper) + 队列(MessageQueue),那么当其它线程之间也需要进行通讯呢?总不能都使用主线程的消息机制吧,这样会乱套的;

那这样的话就给每一个线程设计单独一个轮循器(Looper) + 队列(MessageQueue),用于自身的通讯;那么问题又来了,这么多线程,对应这这么多个轮循器(Looper) + 队列(MessageQueue),要怎么管理也是一个问题;这么多个消息机制,哪个是属于自己线程的,哪个是属于其它线程的,必须要划分好界限才行,不然就会出现a线程想发送消息给b线程,结果发送到c线程去了,这样子就混乱了;

既然如此,那么我们将线程和轮循器(Looper) + 队列(MessageQueue)绑定起来,通过设计一个管理器来管理这些轮循器(Looper) + 队列(MessageQueue),将每一个线程对应的每一个轮循器(Looper) + 队列(MessageQueue)做好一一对应关系;

那么我们就设计一个线程管理类ThreadLocal来管理这些关系,看一下设计出来的效果:

public class ThreadLocal<T> {

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
}

设计两个方法,通过键值对的方式来进行存储与取出,以线程为键,以Looper为值,将其存储起来;

然后在Looper里面将这个ThreadLocal设置为全局唯一的变量,这样其它的线程随时可以通过ThreadLocal来获取自己的Looper;

效果如下:

public final class Looper {
    // 全局唯一的变量,线程随时可以获取到
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    public static void loop() {
        ...
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        // 不停的循环从队列里面取出消息来;
        for (;;) {
            ...
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                ...
            }
            ...
        }
    }
}

6,协同合作

现在每个模块的工具都已经设计完成了,接来下要让这几个模块协同工作,组装成一套完成的消息机制;

Handler:发送消息;
MessageQueue:接收消息的消息队列;
Looper:轮循器,不断的从消息队列里面取出消息;
Message:消息结构体,支持链表结构;
ThreadLocal:管理线程的轮循器(Looper);

这里我们把轮循器(Looper) + 队列(MessageQueue)进行绑定,为一个线程进行服务; 然后在Handler里面通过ThreadLocal来获取自己的Looper,这样发送的消息就会进入Looper里的消息队列(MessageQueue),然后在通过Looper的循环,通过Handler里的接口回调给相应的线程;

让我们看看设计后的结构图:

到这里一套完整的消息机制就设计完成了;

下面来看一下运作的关系图:

Handler的设计很巧妙,源码里的细节很多,我就不贴出来了,这里只是讲一下Handler结构设计,建议可以自己跟着源码去走一遍,用于加深理解!

总结

1,每一个线程都有一个自己的轮循器Looper和消息队列MessageQueue,用于接收其它线程发来的消息,每一个线程都有自己唯一的Looper和MessageQueue;
2,Handler可以无限创建,因为创建的Handler会和线程的Looper进行绑定;
3,Handler发送消息后,会在消息队列里面进行排队,并不会立即被响应到;
4,Handler的消息队列MessageQueue里的消息存在滞后性,因此会存在内存泄露的风险;
5,Handler的Message是链表的结构,用于在消息队列MessageQueue里面进行排队;
6,ThreadLocal用于管理线程的Looper,用来保证其一一对于的关系;

关于我

兄dei,如果我的文章对你有帮助的话,点个赞呗️,也可以关注一下我的Github博客;

欢迎和我沟通交流技术;