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

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

image

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

image

下面我们来看看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博客;

欢迎和我沟通交流技术;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容