欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-09 21:04:57
...

前言:俗话说的好,万丈高楼平地起,学好数理化,走遍天下都不怕;只有先打好基础的情况下,才可以更深入地去学习知识;今天带大家设计一下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;

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

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

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

下面我们来看看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里的接口回调给相应的线程;

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

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

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

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

Android 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博客;

欢迎和我沟通交流技术;

相关标签: Android 源码