Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-03-20:谈谈 Handler 机制和原理?

Moosphan opened this issue · comments

2019-03-20:谈谈 Handler 机制和原理?
commented

这个。。。。
首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法。

首先在主线程创建一个 Handler 对象 ,并重写 handleMessage() 方法。然后当在子线程中需要进行更新UI的操作,我们就创建一个 Message 对象,并通过 Handler 发送这条消息出去。之后这条消息被加入到 MessageQueue 队列中等待被处理,通过 Looper 对象会一直尝试从 Message Queue 中取出待处理的消息,最后分发回 HandlerhandleMessage() 方法中。

什么是handler?我感觉你们说的好麻烦,我感觉就像做辣条一样,最后不都是人吃

@hongcwamazing 来补充一下你的见解吧😁

每一个线程都有一个 handler loop messagequeue机制
线程A要调用线程B
首先线程A会向 线程b的mq中发送一条消息, 线程b的looper就会不断循环取出里面的消息 然后调用线程a的handler的 handlemessage 从而实现了不同线程间通信

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;

B. looper.loop 既然是 while-true 为什么不会卡死?

C. MessageQueue 是队列吗? 他是什么数据结构呢?

D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?

E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?

F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?

G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?

H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?

I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类

J. 你对 IdleHandler 有多少了解?

K. 你了解 HandlerThread 吗?

L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,

哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

commented

说道handler就不得不提 LooperMessageQueueHandler

Looper:它在消息机制里是用来把普通线程转成looper线程的(Looper.prepare()),并担当消息循环的任务,当然,UI 线程是通过Looper.PrepareMainLoper()。
它会开启无限循环(Loper.loop())并不停的从 MessageQueue 中查看是否有新消息,如果有就拿出来处理,如果没有呢,就阻塞(其实真正的阻塞在 MessageQueuenext 里)。Loper在构造的时候会构建一个 MessageQueue 并持有它的引用。

MessageQueue: 消息队列,它用来插入(enqueueMessage),读取消息(next)。它虽然是个消息队列,但实际实现是个单链表,因为单链表在插入和读取上有优势。它插入是根据时间戳排序来的,根据时间戳的排序来指定next的下一条消息。而当next读取的时候,如果有消息,就取出来,如果没有,就阻塞。如果 next 取出来是 null,那么就表示整个app可以结束运行了。

Handler 的主要工作就是负责消息的发送和接收了。发送消息是 postsend 的一堆函数,其实到最后 enqueueMessage() 来进行入队,接收消息处理则是通过msg的next里返回的 looper 在交给 handlerdispatchMessage() 方法。Handler 在哪个线程之下构造就会持有当前这个线程的 looper 引用。Handler 工作的时候首先会判断 msg.callback(Runnable对象)是不是 null,不为 null 就会变为 handlerCallback(执行msg的callback),然后检查 mCallback 是不是 null,不为 null 执行 handlerMessage。而这个 mCallback 则又是通过 msgtarget(对应的handler)来进行回调调用的。

那么整个流程是:activityThreadmain 方法执行即 app 启动的时候,会把当前线程转为 UI 线程(Loper.PrepareMainLoper()),并且会构造 activityThread(下面简称actThread)实例,它的 attach 方法会创建 Binder 线程通道(ApplicationThread,用来接受系统进程传递过来的信息的线程)。然后通过 Looper.loop 来开启无限循环。无限循环开启后,需要一个 handler 和消息队列进行交互,这个handler (ActivityThread.H,简称mH)主要用来管理四大组件的生命周期,启动,停止等消息类型。actThread 通过之前创建的bind线程与ams进行通信,ams执行完actThread的请求后会回调 binder 线程的 bind 方法,然后 binder 线程会向 mH 发送消息,mH 收到消息后会回到 actThread 中去执行。

其实本质的问题就是线程之间怎么通信?一个队列A,一个线程B,一个线程C。

  1. B线程创建各种消息然后丢到A队列中。
  2. C线程是一个while(true)的方法一直在读A队列中的数据,读取到就处理;读取不到就阻塞。

如果抛开Android去处理多线程通信问题就是这样玩的,Android也是,只不过做了很多个性化的处理。

Handler +Message+MessageQueue+Looper

handler做线程间通信用的。
Message  用来做消息本体
MessageQueue 用来做 消息队列 无限循环 
Looper 轮询MessageQueue 看到有消息了 就会拿出来处理。

当有消息需要传递时 可以通过用handler来post/send一个message到MessageQueue里面去,然后looper发现队列里面有消息了 ,拿出来处理,调用这个消息绑定的target,最终调用到handler的handlerMessage()方法。
另外创建handler必须要有个Looper,主线程不需要这么做是因为系统以及自动创建Looper,也自动调用了Looper.loop()开启了循环。
子线程的话 就需要手动 Looper.prepare()来创建looper跟Looper.loop()开启循环。
还有上面说的 开启的死循环 当没有消息时会通过native方法来阻塞的,所以不用害怕会卡死。
至于主线程的消息队列 为什么不会ANR呢? 当没有消息时候,系统会通过linux的管道机制 让主线程进入休眠状态 。而ANR是因为消息没人处理。

commented

之前看艺术探索记录的笔记,android 消息机制及其原理

以下是我读源码的一些感想:

构造方法

首先看构造方法,一共七个构造方法,但是最后都是调用了两个方法,分别是
Handler(Callback callback, boolean async)和Handler(Looper looper, Callback callback, boolean async)。

然后来分析这两个方法

    public Handler(Callback callback, boolean async) {
        //FIND_POTENTIAL_LEAKS是一个私有的静态变量,默认是false,而且整个源码只有这里调用了
        //按道理应该改变不了的
        //看注释是,当设置为true时检测匿名,本地或者成员类继承Handler并且不是静态,这种类型的类会造成潜在的内存泄漏
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            //如果是匿名类或者成员类或者内部类,并且是静态的,警告潜在内存泄露风险
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        //对成员变量赋值,常规操作
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    

再来看看Handler(Looper looper, Callback callback, boolean async)

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到最终都是对mLooper,mQueue,mCallback,mAsynchronous的赋值。

发送消息

再看发送消息,一共七个发送消息的方法,这里我们加上编号。

  • 1.sendMessage(Message msg)
  • 2.sendEmptyMessage(int what)
  • 3.sendEmptyMessageDelayed(int what, long delayMillis)
  • 4.sendEmptyMessageAtTime(int what, long uptimeMillis)
  • 5.sendMessageDelayed(Message msg, long delayMillis)
  • 6.sendMessageAtTime(Message msg, long uptimeMillis)
  • 7.sendMessageAtFrontOfQueue(Message msg)

通过观察我们可以看到他们的关系

  • 1-->5-->6
  • 2-->3-->5-->6
  • 4-->6
  • 6-->enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
  • 7-->enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

这个有一个知识点,我们看到最后都是调用的1,2,3,4,5最后都是调用sendMessageAtTime这个方法。那么发送延时消息是怎么计时的呢?举一个源码的例子

    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

注意这里SystemClock.uptimeMillis() + delayMillis。SystemClock.uptimeMillis()这个方法返回的官方注解Returns milliseconds since boot, not counting time spent in deep sleep.。个人理解手机启动后未休眠状态的时间。Android 7.0之后手机息屏太久会进入休眠。可以看到会计算出延时后的时间,然后进行入队列。那么入队列又是怎么操作的,继续看。

入队列,进入消息池

看名字就是消息入队列了。看下源码

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //Handler target是Message的成员变量
        msg.target = this;
        if (mAsynchronous) {
            //是否异步
            msg.setAsynchronous(true);
        }
        //MessageQueue执行入队列操作
        return queue.enqueueMessage(msg, uptimeMillis);
    }

接着看MessageQueue的enqueueMessage方法

    boolean enqueueMessage(Message msg, long when) {
        //handler为空抛异常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //消息已经进入队列
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            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;
            
            //当前队列的队头msg
            Message p = mMessages;
            boolean needWake;
            //解释下when < p.when :这个消息触发时间比p早,所以把这个消息放在P之前,也就是队头
            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 {
                //否则也就是需要放在队列的队头msg之后
                //关于needWake回头再研究
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //遍历循环找到要插入的位置
                for (;;) {
                    将当前的P赋值给临时变量prev
                    prev = p;
                    p = p.next;//p赋值p的next
                    
                    //如果没有下一个,或者当前的触发时间早于P的触发时间,跳出循环
                    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;
    }

至此,入队列完全结束。

处理消息

正常情况下,发消息流程到此结束。但是好奇的你一定发现消息发送完成了,谁处理啊?Looper啊,Looper.loop();但是这个方法要手动调用。有经验的一定知道Activity中不需要手动调用是因为在Activity中系统已经帮我们调用了。

至于Looper.loop()方法,这里简单提一下。在入队列时,我们已经按消息触发时间排好序了,那么Looper.loop()要做的就是不断取出队列头的msg,调用Handle.dispatchMessage(msg)。

    public static void loop() {
    
        ······
        
        for (;;) {
        
        
            //拿到队头的消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ······
            try {
                //msg.target.dispatchMessage(msg)其实就是Handler.dispatchMessage(msg);
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ······
        }
    }

细心的你一定又发现,延迟消息在队头,又不到时间怎么处理?当然有办法处理,我们看下queue.next()的源码

    Message next() {
    
        ···省略代码···
        
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                //拿到当前系统未休眠时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //当前时间小于消息时间,也就是还不到消息触发的时间
                    if (now < msg.when) {
                       nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //到了消息触发的时间
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }

                ···省略代码···
        }
    }

也就是只有到了消息触发的时间调用next才会返回msg。至此全部结束。

更详细可以看我写的博客:一篇文章搞懂Handler发消息时,Handler,MessageQueue,Looper都做了些什么

commented

最近刚好在看Android的消息处理机制,主要参照《Android开发艺术探索》,做了一个关于 Handler 的思维导图:
Android的消息处理机制

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;

B. looper.loop 既然是 while-true 为什么不会卡死?

C. MessageQueue 是队列吗? 他是什么数据结构呢?

D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?

E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?

F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?

G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?

H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?

I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类

J. 你对 IdleHandler 有多少了解?

K. 你了解 HandlerThread 吗?

L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,

哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;

B. looper.loop 既然是 while-true 为什么不会卡死?

C. MessageQueue 是队列吗? 他是什么数据结构呢?

D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?

E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?

F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?

G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?

H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?

I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类

J. 你对 IdleHandler 有多少了解?

K. 你了解 HandlerThread 吗?

L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,

哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

F是为什么呀 ?为什么收不到消息

commented

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;
B. looper.loop 既然是 while-true 为什么不会卡死?
C. MessageQueue 是队列吗? 他是什么数据结构呢?
D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?
E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?
F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?
G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?
H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?
I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类
J. 你对 IdleHandler 有多少了解?
K. 你了解 HandlerThread 吗?
L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,
哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;
B. looper.loop 既然是 while-true 为什么不会卡死?
C. MessageQueue 是队列吗? 他是什么数据结构呢?
D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?
E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?
F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?
G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?
H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?
I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类
J. 你对 IdleHandler 有多少了解?
K. 你了解 HandlerThread 吗?
L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,
哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

F是为什么呀 ?为什么收不到消息

因为return掉了

Android消息机制---Handler:将某个任务切换到某个指定的线程去执行。

**Message:**在线程之间传递的消息,它的内部持有Handler和Runnable的引用以及消息类型。可以使用what、arg1、arg2字段携带一些整型数据,使用obj字段携带Object对象;其中有一个obtain()方法,该方法的内部是先通过消息池获取消息,没有再创建,实现了对message对象的复用。其内部有一个target引用,就是对Handler对象的引用,在Looper.loop方法中的消息处理就是通过message的target引用来调用Handler的dispatchMessage()方法来实现消息的处理。

MessageQueue:消息队列,是通过一个单链表的数据结构维护消息列表的,在插入和删除有优势。其中主要包括两个操作:插入和读取,读取操作本身伴随着删除操作。插入操作是enqueueMessage()方法,就是插入一条消息到MessageQueue中;读取操作是next()方法,它是一个无限循环,如果有消息就返回并从单链表中移除;没有消息就一直阻塞(此时主线程会释放CPU进入休眠状态)。

Handler:主要包括消息的发送和接收。发送消息(就是往MessageQueue里面插入一条Message)通过post方法和send方法,而post方法最终也是通过send方法来发送的,最终就会调用sendMessageAtTime这个方法(内部就是调用MessageQueue的enqueueMessage()方法,往MessageQueue里面插入一条消息),同时也会给msg的target赋值为handler本身,进入MessageQueue中。处理消息就是Looper调用loop()方法进入无限循环,获取到消息后就会调用msg.target(Handler本身)的dispatchMessage()方法,进而调用handlerMessage()方法处理消息。

dispatchMessage():首先检查msg的callBack是否为空,message的Callback就是Runnable对象,不为空就调用handleCallback()方法,最终会调用Runnable的run方法处理消息。其次是检查mCallBack是否为空,不为空就调用mCallBack的handleMessage()方法,否则就调用handleMessage()方法处理消息。

ThreadLocal:它是线程内部的一个数据存储类。可以在不同的线程中互不干扰的存储并提供数据。主要有set()和get()两个方法,线程的数据主要存储在ThreadLocal的成员localValues 内部的 table数组 中。所有线程共享 一个ThreadLocal对象。

ThreadLocal使用场景:1.某些数据以线程为作用域,并且不同线程有不同数据副本时。如Looper的作用域就是线程,不同的线程有不同的Looper,通过ThreadLocal可以实现Looper在线程中的存取;如EventBus,EventBus需要获取PostingThreadState对象,不同的PostingThreadState对象作用于不同的线程,也可以通过ThreadLocal获取当前线程下的PostingThreadState对象。2.复杂逻辑下的对象传递,如监听器的传递

Looper:在构造函数中,创建了一个MessageQueue,因为一个线程只能有一个Looper对象否则会抛出异常,这也保证了一个线程只有一个MessageQueue对象;获取到了当前线程,完成了与当前线程的绑定。

主要有prepare()和loop()两个方法:
prepare():创建一个Looper对象,并存储到ThreadLocal对象中。
首先判断有没有Looper对象,没有就创建一个Looper对象,保存到ThreadLocal对象中;有的话就抛出异常。这样就保证一个线程只能有一个Looper对象。

loop():从无限循环中获取到消息传递到Handler的dispatchMessage中,再调用handleMessage处理消息。
首先调用myLooper()方法,获取到ThreadLocal中保存的Looper对象,然后通过该Looper得到对应的MessageQueue对象,然后会进入无限循环,通过调用MessageQueue的next()方法获取消息,有消息就取出消息然后交给Message所持有的Handler,也就是会执行msg.target(Handler本身)的dispatchMessage()方法处理消息;没有消息就一直阻塞(此时主线程会释放CPU进入休眠状态)。

注意:1.Looper还提供了getMainLooper()方法用来获取主线程的Looper。2.Looper也是可以退出的,提供了quit()和quitSafely()方法,其中quit()是直接退出Looper,quitSafely()是处理完消息队列中的消息再退出。当Looper的quit()被调用的时候会调用MessageQueue的quit()方法,此时next()方法返回null(是退出死循环的唯一方式),然后loop()方法就会退出。

为什么不能在子线程更新UI:Android中的UI控件并不是线程安全的,多线程的并发访问可能会导致UI控件处于不可控状态。

为什么不加锁机制:一是加锁机制会让访问逻辑变得复杂,二是加锁机制会阻塞某些线程的,降低UI的访问效率。

注意:

1.线程默认是没有Looper的,如果在子线程使用Handler必须先创建Looper否则会发生异常。

2.经常提到的主线程,也叫UI线程,就是ActivityThread,ActivityThread被创建时就会初始化Looper,所以默认在主线程中可以使用Handler。

3.一个Looper对应一个MessageQueue;一个线程对应一个Looper;一个Looper可以对应多个Handler;

@Alex-Cin @Moosphan
大佬们,能不能看看回答的哪些地方需要完善或者有问题的呢?

Android消息机制和原理

1.Looper.loop()和MessageQueue.next()取延时消息时,都在主线程中使用了死循环为什么不会卡死?

答:因为在MessageQueue在取消息时,如果是延时消息就会计算得到该延时消息还需要延时多久nextPollTimeoutMillis。然后再继续循环的时候,发现nextPollTimeoutMillis不等于0,就会执行nativePollOnce阻塞线程nextPollTimeoutMillis毫秒,而阻塞了之后被唤醒的时机就是阻塞的时间到了或者又有新的消息添加进来执行enqueueMessage方法调用nativeWake唤醒阻塞线程,再继续执行获取消息的代码,如果有消息就返回,如果还是需要延时就继续和上边一样阻塞。而Android所有的事件要在主线程中改变的都会通过主线程的Handler发送消息处理,所以就完全保证了不会卡死。

其中nativePollOnce的的位置也有考究,刚好在synchronized的外边,所以在阻塞的时候也能保证添加消息是可以执行的,而取消息时添加消息就需要等待。

nativePollOnce和nativeWake都是底层方法,调用链:

  • nativePollOnce --> android_os_MessageQueue.pollOnce --> Looper.pollOnce --> Looper.pollInner --> epoll_wait实现阻塞;epoll_wait是linux方法就是进行阻塞一段时间,返回的eventCount如果出错或者超时就会执行goto Done处,或者有事件发生就继续执行如果fd是mWakeEventFd就唤醒。
  • nativeWake --> android_os_MessageQueue.wake --> Looper.wake 实现唤醒。

对于wake方法里如何实现唤醒还需要再学习,目前看不懂

2.MessageQueue是队列吗?它是什么数据结构?

答:MessageQueue不是队列,它内部使用一个Message链表实现消息的存和取。

3.Handler的postDelay,时间准吗?为什么呢?它用的是system.currentTime吗?

答:不准,因为looper里边从MessageQueue里取出来的消息执行也是串行的,如果前一个消息是比较耗时的,那么等到执行之前延时的消息时时间难免可能会超过延时的时间。postDelay时用的是System.uptimeMillis,也就是开机时间。

4.子线程run方法中如何使用Handler?

答:先要使用Lopper.prepare方法,然后使用该looper创建一个Handler,最后调用Looper.loop方法;Looper.loop方法之后就不要执行写代码了,因为是loop是死循环除非退出,所以Handler的创建也必须写在loop之前。

5.ThreadLocal是如何实现一个线程一个Looper的?

答:Looper的使用最终都需要执行loop方法,而loop方法中去获取的Looper是从sThreadLocal中获取的,所以Looper就需要和sThreadLocal建立关系,在不考虑反射的情况下,就只能通过Looper的prepare方法进行关联,这里边就会引入一个threadLocalMap,该对象又是和thread一一对应,而threadLocal的get方法实际使用的就是threadLocalMap的get方法,而key就是Looper中的静态变量sThreadLocal,value则就是当前looper对象,而prepare方法只能被执行一次,也就保证了一个线程只有一个looper。如果考虑反射的话,因为必须和sThreadLocal建立关系,所以哪怕给sThreadLocal设置进去了反射创建的looper,也会因为key相同而覆盖上一个looper,最终还是只有一个线程对应一个looper。其中ThreadLocalMap对key和value的存取和hashMap类似。

6.假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?

答:先传入一个延时为10ms的消息进入MessageQueue中,因为该消息延时,假设当前消息队列中没有消息,则会直接将消息放入队列,因为loop一直在取消息,但是这里有延时就会阻塞10ms,当然这不考虑代码执行的时间;然后延时1ms的消息进入时,会和之前的10ms的消息进行比较,根据延时的大小进行排序插入,延时小的在前边,所以这时候就把1ms的消息放在10ms的前边,然后唤醒,不阻塞,继续执行取消息的操作,发现还是有延时1ms,所以也会继续阻塞1ms,直到阻塞1ms之后或者又有新的消息进入队列唤醒,直到获取到1ms延时消息,在loop中,通过调用handler的dispatchMessage方法,判断消息的callback或者Handler的callback不为null就回调对应的callback,否则就执行handler的handleMessage方法,我们就可以根据情况处理消息了;10ms的延时消息的处理也是一致,延时的时间到了就交给返回给looper,然后给handler处理。

7.主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类

答:在ActivityThread类的main方法中被第一次调用,是在应用被打开的时候执行。

8. 你对 IdleHandler 有多少了解?

答:IdleHandler是一个接口,在MessageQueue的next方法没有获取到消息或者获取到延时消息时,就会执行IdleHandler的queueIdle方法进行回调;可以用于监听更丰富的生命周期回调,比如想在view完全绘制完成之后调用,因为onResume之后view并不一定会完全绘制完成,但是内部的绘制和onResume等都是通过主线程的Handler发送的消息处理的,消息的处理又是串行的,所以想要在完全绘制完成之后回调就完全可以在onCreate的setContentView之后为主线程的MessageQueue添加一个IdleHandler接口,它的回调方法,返回true表示该回调一直都存在,只要空闲就回调,返回false表示只回调一次就移除。

9.你了解 HandlerThread 吗?

答:HandlerThread是Thread的一个子类,只是内部创建了一个Handler,这个Handler是子线程的handler,其中子线程的looper的创建和管理也提供了方法方便使用。

10.你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗?

答:Message.obtain其实是从缓冲的消息池中取出第一个消息来使用,避免消息对象的频繁创建和销毁;消息池其实是使用Message链表结构实现,在消息在loop中被handler分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表最前边。

F是为什么呀 ?为什么收不到消息

子线程中使用 handler, 我们假设这个handler 不去更新UI, 所以用的不是MainLooper, 就是接受数据, 我们知道迭代消息是 while-true 的形式, 那么MainLooper怎么不会卡主线程呢, 因为把主线程挂起了, 刺激吧, 你没有听错, 那么问题又来了, 主线程就是UI线程, UI线程挂起了, 我怎么感觉不到呢? 不是因为你眼睛不好使, 而是因为 vSync 太快, 1秒钟就有60个心跳, 假设点击了按钮, 让它变颜色, 之所以能变颜色, 是因为新的刷新信号过来了, vSync 会传到mainLooper 的, 有消息放到mainLooper, 就会唤醒主线程. 回复的时候, 没有看源码, 是按照记忆中敲的, 整体思路是对的, 有细节描述不当.
@missyyyou

谢邀, 其实我是个菜鸡, 我柔弱的身躯加上微薄的薪水, 大佬这个词汇, 不太适合称呼我;
我的答案, 无果有误导, 记得回复一下, 我在做修改,
1.. 对于wake方法里如何实现唤醒还需要再学习,目前看不懂?
我不知道, 我回答的有没有切入正题,
只要有消息进来, 就会执行 MessageQueue#enqueueMessage, 这个方法体, 有一句话是 nativeWake(mPtr);
意思是, 只要有新的消息过来, 就会唤醒主线程; 我是这样理解的, 虚拟机有一个队列, 叫挂起队列, 进入这个队列的线程, 会主动放弃使用CPU; 至于, 为什么莫名其妙的有新消息进来, 我记得是这里 Choreographer#scheduleFrameLocked ;

2.. 10.你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗?
这个问题, 我之前在网上, 好像没有找到合适的答案, 或者...半年前了, 不记得为什么没有读书笔记了, 我先贴一下, 当时看源码, 自己的感受吧, 错了, 别怪我菜鸡;
A.. 我们假设, 每次 post/send 一个消息, 用 obtain 的话,
第一次 sPool=null, 会 new 出来一个 Message 对象 m0, 在处理完消息的时候, looper 会调用 msg.recycleUnchecked(), 那么, 这个 m0 会被赋值给 sPool;
第二次, 或者第 N 次, 用 obtain 的话, 永远都是使用的 m0, 这样就达到了消息的复用;

B.. 我们假设, 有一次同时 post/send 8 条消息, 这里的同时的意思是, 在 looper 没有调用 msg.recycleUnchecked()之前, 所以继续看 obtain 方法;
如果先加锁, 持有这把锁的是 public static final Object sPoolSync = new Object();
所以所有的调用处都要在外面排队等候, 当发现 sPool 非空时, 就使用链表头结点, 一直占用, 直到链表用光了, sPool 就会变成 null, 就会重新 new Message;
当然链表的 next 节点, 不会很多, 最大值是 50 个, 超出 50 的部分, 不会被加到链表 next 节点上, 也就是不会放在消息池中;

消息池如果增加到 8 个, 不会降下来, 如果继续增加到 16 个, 也不会降下来, 可以理解为, 核心池数量, 只要产生了峰值, 就会一直持有者;
@arvinljw

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;
B. looper.loop 既然是 while-true 为什么不会卡死?
C. MessageQueue 是队列吗? 他是什么数据结构呢?
D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?
E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?
F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?
G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?
H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?
I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类
J. 你对 IdleHandler 有多少了解?
K. 你了解 HandlerThread 吗?
L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,
哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

大概是这样的, Handler Message MessageQueue Looper;
A. messageQueue.next 是阻塞式的取消息, 如果有 delay 会调用 nativeWake;
那么问题来了, 线程挂起了, 是挂起的 UI线程吗? 答案是 YES, 为什么我没有察觉呢?
还有就是 nativeWake 和 nativePollOnce 的实现原理;
B. looper.loop 既然是 while-true 为什么不会卡死?
C. MessageQueue 是队列吗? 他是什么数据结构呢?
D. handler 的postDelay, 时间准吗? 答案是不准, 为什么呢?
E. handler 的 postDelay 的时间是 system.currentTime 吗? 答案是 NO, 你知道是什么吗?
F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?
G. handler 这么做到的 一个线程对应一个 looper, 答案是threadLocal, 你对ThreadLocal 有什么了解吗?
H. 假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?
I. 你知道主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类
J. 你对 IdleHandler 有多少了解?
K. 你了解 HandlerThread 吗?
L. 你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗,
哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

F是为什么呀 ?为什么收不到消息

looper.loop()是个死循环

@Alex-Cin @Moosphan
大佬们,能不能看看回答的哪些地方需要完善或者有问题的呢?

Android消息机制和原理

1.Looper.loop()和MessageQueue.next()取延时消息时,都在主线程中使用了死循环为什么不会卡死?

答:因为在MessageQueue在取消息时,如果是延时消息就会计算得到该延时消息还需要延时多久nextPollTimeoutMillis。然后再继续循环的时候,发现nextPollTimeoutMillis不等于0,就会执行nativePollOnce阻塞线程nextPollTimeoutMillis毫秒,而阻塞了之后被唤醒的时机就是阻塞的时间到了或者又有新的消息添加进来执行enqueueMessage方法调用nativeWake唤醒阻塞线程,再继续执行获取消息的代码,如果有消息就返回,如果还是需要延时就继续和上边一样阻塞。而Android所有的事件要在主线程中改变的都会通过主线程的Handler发送消息处理,所以就完全保证了不会卡死。

其中nativePollOnce的的位置也有考究,刚好在synchronized的外边,所以在阻塞的时候也能保证添加消息是可以执行的,而取消息时添加消息就需要等待。

nativePollOnce和nativeWake都是底层方法,调用链:

  • nativePollOnce --> android_os_MessageQueue.pollOnce --> Looper.pollOnce --> Looper.pollInner --> epoll_wait实现阻塞;epoll_wait是linux方法就是进行阻塞一段时间,返回的eventCount如果出错或者超时就会执行goto Done处,或者有事件发生就继续执行如果fd是mWakeEventFd就唤醒。
  • nativeWake --> android_os_MessageQueue.wake --> Looper.wake 实现唤醒。

对于wake方法里如何实现唤醒还需要再学习,目前看不懂

2.MessageQueue是队列吗?它是什么数据结构?

答:MessageQueue不是队列,它内部使用一个Message链表实现消息的存和取。

3.Handler的postDelay,时间准吗?为什么呢?它用的是system.currentTime吗?

答:不准,因为looper里边从MessageQueue里取出来的消息执行也是串行的,如果前一个消息是比较耗时的,那么等到执行之前延时的消息时时间难免可能会超过延时的时间。postDelay时用的是System.uptimeMillis,也就是开机时间。

4.子线程run方法中如何使用Handler?

答:先要使用Lopper.prepare方法,然后使用该looper创建一个Handler,最后调用Looper.loop方法;Looper.loop方法之后就不要执行写代码了,因为是loop是死循环除非退出,所以Handler的创建也必须写在loop之前。

5.ThreadLocal是如何实现一个线程一个Looper的?

答:Looper的使用最终都需要执行loop方法,而loop方法中去获取的Looper是从sThreadLocal中获取的,所以Looper就需要和sThreadLocal建立关系,在不考虑反射的情况下,就只能通过Looper的prepare方法进行关联,这里边就会引入一个threadLocalMap,该对象又是和thread一一对应,而threadLocal的get方法实际使用的就是threadLocalMap的get方法,而key就是Looper中的静态变量sThreadLocal,value则就是当前looper对象,而prepare方法只能被执行一次,也就保证了一个线程只有一个looper。如果考虑反射的话,因为必须和sThreadLocal建立关系,所以哪怕给sThreadLocal设置进去了反射创建的looper,也会因为key相同而覆盖上一个looper,最终还是只有一个线程对应一个looper。其中ThreadLocalMap对key和value的存取和hashMap类似。

6.假设先 postDelay 10ms, 再postDelay 1ms, 你简单描述一下, 怎么处理这2条消息?

答:先传入一个延时为10ms的消息进入MessageQueue中,因为该消息延时,假设当前消息队列中没有消息,则会直接将消息放入队列,因为loop一直在取消息,但是这里有延时就会阻塞10ms,当然这不考虑代码执行的时间;然后延时1ms的消息进入时,会和之前的10ms的消息进行比较,根据延时的大小进行排序插入,延时小的在前边,所以这时候就把1ms的消息放在10ms的前边,然后唤醒,不阻塞,继续执行取消息的操作,发现还是有延时1ms,所以也会继续阻塞1ms,直到阻塞1ms之后或者又有新的消息进入队列唤醒,直到获取到1ms延时消息,在loop中,通过调用handler的dispatchMessage方法,判断消息的callback或者Handler的callback不为null就回调对应的callback,否则就执行handler的handleMessage方法,我们就可以根据情况处理消息了;10ms的延时消息的处理也是一致,延时的时间到了就交给返回给looper,然后给handler处理。

7.主线程的Looper, 第一次被调用loop方法, 在什么时候吗? 哪一个类

答:在ActivityThread类的main方法中被第一次调用,是在应用被打开的时候执行。

8. 你对 IdleHandler 有多少了解?

答:IdleHandler是一个接口,在MessageQueue的next方法没有获取到消息或者获取到延时消息时,就会执行IdleHandler的queueIdle方法进行回调;可以用于监听更丰富的生命周期回调,比如想在view完全绘制完成之后调用,因为onResume之后view并不一定会完全绘制完成,但是内部的绘制和onResume等都是通过主线程的Handler发送的消息处理的,消息的处理又是串行的,所以想要在完全绘制完成之后回调就完全可以在onCreate的setContentView之后为主线程的MessageQueue添加一个IdleHandler接口,它的回调方法,返回true表示该回调一直都存在,只要空闲就回调,返回false表示只回调一次就移除。

9.你了解 HandlerThread 吗?

答:HandlerThread是Thread的一个子类,只是内部创建了一个Handler,这个Handler是子线程的handler,其中子线程的looper的创建和管理也提供了方法方便使用。

10.你对 Message.obtain() 了解吗, 或者你知道 怎么维护消息池吗?

答:Message.obtain其实是从缓冲的消息池中取出第一个消息来使用,避免消息对象的频繁创建和销毁;消息池其实是使用Message链表结构实现,在消息在loop中被handler分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表最前边。

Good Job!对于问3我补充一点看法,为什么使用开机时间而不是系统时间。如果使用系统时间,系统时间有set方法,是可以人为设置或者网络矫正,可以往前也可以往后,造成延时操作的不可控性。而使用开机时间除非深度睡眠不然是不会停止的,无法手动设置。

大概是这样的, Handler Message MessageQueue Looper;
…… (适当做个省略)
F. 子线程run方法使用 handler 要先 looper.prepare(); 再 handler.post; 再 looper.loop();
那么问题来了, looper.loop(); 之后 在 handler.post 消息, 还能收到吗? 答案是 NO, 为什么?
…… (适当做个省略)
哈哈, 事实上 关于 handler还有一些其他的问题, 我都不记得了;

F是为什么呀 ?为什么收不到消息

其实直接回答成No 是很不严谨的。
这个要看在那个线程调用 handle.post方法。

这个问题的根本在于 Looper.loop之后,调用的子线程(起个名字叫A子线程 )其实已经处在for(;;) 无限循环的爱情魔力之中了。所以在 A 子线程中, loop 方法调用之后,任何代码都是一时半会执行不到的(但无果无线循环爱情魔力结束后可以执行到的)。
所以针对问题 handle.post 如果实在其他线程调用,其实是可以收到消息的。
严谨。

可以简单写个demo尝试一下。

public class MainActivity extends AppCompatActivity {

  private static final String TAG = MainActivity.class.getSimpleName();
  Handler handlerForSubThread;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  
    findViewById(R.id.postSubThread).setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        handlerForSubThread.dump(new LogPrinter(Log.VERBOSE, TAG), "JerryTest.2");

        handlerForSubThread.post(new Runnable() {
          @Override public void run() {
            Log.d(TAG, "got and  run Runnable 2");
          }
        });
      }
    });

    Log.d(TAG, "onCreate method: currentThread is: " + Thread.currentThread());

    new Thread(new Runnable() {
      @Override public void run() {
        Looper.prepare();

        handlerForSubThread = new Handler(){

          @Override public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.d(TAG, "handleMessage  msg: " + msg +  ". currentThread is: " + Thread.currentThread());

          }
        };

        handlerForSubThread.post(new Runnable() {
          @Override public void run() {
            Log.d(TAG, "got and  run Runnable 1");
          }
        });

        handlerForSubThread.sendEmptyMessage(1);
        handlerForSubThread.postDelayed(new Runnable() {
          @Override public void run() {
            Log.d(TAG, "got and  run runable 3");
          }
        }, 5000);

        handlerForSubThread.dump(new LogPrinter(Log.VERBOSE, TAG), "JerryTest.1");

        Objects.requireNonNull(Looper.myLooper()).setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
        Looper.loop();

        Log.d(TAG, "after looper.loop");

      }
    }).start();
  }

  @Override protected void onDestroy() {
    super.onDestroy();
    handlerForSubThread.getLooper().quitSafely();
  }
}

Android 常见面试题,建议看Android艺术探索第十章;
然后应该问,为啥UI线程也是Looper,为啥它不阻塞
https://blog.csdn.net/u011418943/article/details/106813879

handler 主子线程间发送消息 接收到message消息后线程也会切换掉
message 消息
MessageQueue 消息队列
Looper 轮询
通过handle发送message消息到消息队列中 looper论询器会一起监听轮询消息队列中是否有消息 如果有则执行消息

ondestroy的时候要removeCallbacksAndMessages