Handler-Message的流程分析,及常见问题讨论

Handler-Message的流程分析,及常见问题讨论

一、handler-message的使用场景:

现在要求在一个子线程里面发送一个消息到主线程去跟新UI的数据,使用handler-message来实现这个问题:


1.png

在子线程里面创建了一个message,给message分配了一个tag,作为标记,区别不同的message,
将message封装在msg.obj中,然后通过handler.sendMessage给发送给对应的Handler执行handlerMessage 来接收消息改变UI

二、handler-message的源码分析

handler-message中各个重要的类:

1: Handler: 主线程与子线程的消息媒介, 负责发送和处理消息
2: Looper: 消息队列和handler的通讯媒介, 负责线程的关联和消息的分发,在该线程中获取到messageQueue里面的message,并且分发给handler.
3: Message: 线程中通讯的数据单元, final修饰,实现Parcelable接口, 可用于进程间的数据传递.
4: MessageQueue: 是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。

handler机制的流程:

在主线程里面创建 消息处理器Looper, 消息队列MessageQueue, Handler对象.

1: 主线程之中, Looper的创建已经在ActivityThread中的main方法中默认初始化了.


2.png

执行了Looper.prepareMainLooper() 和 Looper.loop()


3.png

4.png

5.png

2: 在prepareMainLooper()执行了Looper.prepare()方法,prepare()里面创建了一个Looper, 获取当前的线程并且初始化了MessageQueue, 然后将这个looper放在ThreadLocal中去.(此时ThreadLocal是有Looper的哈)
紧接着执行looper.looper(),开启无线循环来进行消息的分发.


6.png

开启死循环,不断的从消息队列中取出消息,然后对消息进行分发.
问题: 为什么Looper的死循环没造成UI线程卡死.
7.png

喔嚯: * 在looper.looper()之后不再执行其余的操作, 然后将异常抛出去. 所以死循环可以保证UI线程不会被退出, Android界面绘制都是通过Handler消息来实现的,这样可以让界面保持可绘制的状态*
handler的构造函数

既然在ActivityThread启动起来了,我们准备好了Looper, MessageQueue,并且创建了主线程的Handle, Handler, Looper, MessageQueue的关系形成了1:1:1
在默认的构造函数里面, handler是没有传looper的,这是因为looper和messageQueue在ActivityThread的main()函数里面是已经被初始化了的, 在这个构造函数里面通过Looper.myLooper方法去通过get()在ThreadLocal里面拿到对应的looper, 进而进行赋值绑定, 此时handler就跟looper以及messageQueue进行绑定了的.


8.png

上图是默认构造,传参里面是没有looper的
没有looper的情况使用Looper.looper来绑定当前线程, 那么看看他是怎么绑定当前线程的:


9.png

10.png

如上图所示:可以看到该方法通过ThreadLocal来拿到当前线程绑定的Looper. (此举就是handler拿到当前线程的looper和messageQueue的引用)

Looper 取出消息,如何发到制定的Handler上面去

前面见到了在创建整个应用的时候,会执行looper.loop(), 形成死循环,不断读取messageQueue的消息.

Message msg = queue.next(); 循环读取
msg.target.dispatchMessage(msg); msg.target = hander =>使用对应的handler进行分发

Message的发送过程:
11.png

在发送消息的时候,我们需要使用handler.obtainMessage 或者 Message.obtain 出来一个message, 在message里面存载一些数据, 然后通过handler发送出去.
1: handler发送数据的方式:
sendMessage: 发送一个消息
sendEmptyMessage: 发送一个空的消息
sendEmptyMessageDelayed: 延迟发送空消息
sendEmptyMessageAtTime: 某个时间发送一个空消息
sendMessageDelayed: 延迟发送一个消息
sendMessageAtTime: 某个时间发送一个消息
sendMessageAtFrontOfQueue:发送一个消息到队列之前
2: 我们来跟踪一下sendMessage的源码, 看他是做了哪些操作:


12.png

根据代码,我们看到了调用到了enqueueMessage这个方法里面来了:
可以看到在这个方法里面message 的target绑定了当前的handler, 然后将这个消息发送到消息队列里面去了, 那么此时消息队列的message就不为null了,looper就看似读取messageQueue,取出里面的消息,发送给对应的handler进行处理.

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

重点看这个if - else里面的代码:

1 p是当前的messageQueue的首个message, 如果当前队列没有其他需要发送的message, 或者当前新添加进来的Message的时间节点为0(代表需要立即发送的消息),
或者当前新添加进来的的Message需要发送的时间点小与当前MessageQueue队列头部Message的时间点(即当前添加的message需要在当前的messageQueue队列的头部message之前被发送), 就会进入到if代码块中, 即现在要做的就是吧当前的message插入到队列第一个.
msg.next = p; mMessages = msg; needWake = mBlocked 这就是一个插入的动作
2 messageQueue内部是按照发送时间从小到大排列的,当队首的message未达到发送的时间点时候, 线程会被阻塞. 所以这里需要根据线程是否注射来判定是否需要唤醒线程, 则有唤醒了线程才能及时的把要发送出去的消息发送出去. 唤醒线程是nativeWake()去唤醒,只有线程在唤醒状态才能把消息发送出去.
3: 说明了当前添加进来的Message是在当前MessqgeQueue队首的Message之后才会被发送的,上边分析if部分代码的时候说过了Message是按需要发送的时间先后排列在MessageQueue中的,这里的for循环实际操作就是找到MessageQueue中比当前添加进来的Message需要发送的时间点大的位置,将Message插入到其前边(实际就是一个链表的插入操作)。

handleMessage 接收并且处理消息

当handler把消息发送到对应的handler进行处理的时候,需要重写handleMessage的方法, 这个方法里面是怎么拿到消息的呢?

1 我们回到Looper.loop()中去, 这里创立了一个死循环, 不断的从messageQueue里面去拿消息.然后将消息发送到对应的target 也就是handler去


13.png

2 调用dispatchMessage 进行消息的处理, 也就是handlerMessage.
3 那么这个handleMessage是不是很熟悉, 对, 没错,就是我们经常重写的Handler里面的回调方法, 在handler里面handlerMessage是个空实现,等着用户进行重写对消息进行自定义的处理.

整个过程的流程图: (主线程的handler, 实质上在子线程里面handler也是同一个道理和流程,只是ActivityThread把我们需要做的事情已经做了的)


handle-message.png
如何在子线程里面创建handler,主线程发送消息,子线程接收消息

请看下面的demo:


14.png

15.png

开启了一个子线程,在子线程里面处理消息.

1: 创建一个looper
2: 重写里面的方法
3: 循环读取messageQueu

问: 此处的looper可不可以省略, 他的主要的作用是什么? 为什么要这么写? 出现的一些常见的问题:

1: 在子线程中没有默认创建Looper, 在执行handler的myLooper()的时候, 在ThreadLocal里面去获取对应的looper, 但是在子线程里面并没有创建looper.


16.png

2: 所以在创建Handler之前,执行Looper.prepare(), 看看prepare()里面的实现.


17.png

如果looper已经创建了不等于null, 那么就直接抛出异常. 这说明了: 每个线程只能创建一个Looper, 使用这个Looper来管理对应线程的messageQueue.
3: Handler messageQueue Looper之间的对应关系:

handler跟其余几个都没有对应的关系, 线程可以创建多个Handler, 一个线程对应了一个looper和messageQueue.

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

推荐阅读更多精彩内容