美文网首页
虽迟但到-Handler

虽迟但到-Handler

作者: JackDaddy | 来源:发表于2020-11-13 20:02 被阅读0次

前言

HandlerAndroid 中用到 Handler 的地方非常多,无论是系统线程还是要自己开线程去刷新界面,都需要用到 Handler,可以说 HanlerAndroid 中的地位非常高,因此弄懂 Handler 是非常必要的。

Handler 的使用

相信很多小伙伴对于 Handler 的印象都是用来处理线程间通信。没错 Handler 的一大用处确实是用来线程间通信:

private Handler handler;

Thread AThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //休眠 2s 再发送是为了防止 handler 发送消息时 handler 还没初始化好
                Message msg = new Message();
                msg.what = 1;
                handler.sendMessage(msg);
            }
        });

        Thread BThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //Looper.prepare();
                handler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
              //Looper.loop();
            }
        });

        AThread.setName("Send");
        AThread.setName("Receive");
        AThread.start();
        BThread.start();

运行以上代码会发生崩溃,同时看到报错日志:

 java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-3,5,main] that has not called Looper.prepare()

根据这个报错,发现是没有在初始化 Handler 之前调用 Looper.prepare(); 方法。接着把注释放开,运行,成功看到接受日志,这个时候会说另外一种写法的时候为什么不会报这个错呢:

Handler handler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg != null) {
                Log.e("msg", "receive:" + msg.what);
            }
        }
    };

      new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                handler.sendMessage(message);
            }
        }).start();

可以看到这个 Handler 在主线程进行初始化,从子线程发送消息,在子线程接收消息。根据上面的打印日志,猜测在主线程的时候是否执行了 Looper.prepare(); 方法。在 ActivityThread 中的 main() 方法中,找到了 Looper.prepareMainLooper(); 方法:

public static void main(String[] args) {
        //省略代码······
      
        //这里相当于执行 Looper.prepare()
        Looper.prepareMainLooper();

        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //依旧需要执行这个方法
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

先来看一看 Looper.prepareMainLooper();Looper.prepare(); 方法的区别是什么:

   public static void prepare() {
        //调用下面的 prepare 方法
        prepare(true);
    }

   private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //往 ThreadLocal 里set Looper
        sThreadLocal.set(new Looper(quitAllowed));
  }
=====================================================================
    public static void prepareMainLooper() {
        //这里调用直接设成 false
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

首先,这里看到了 sThreadLocal 这个变量,点进去发现:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

全局只有一个,同时不可更改。

Looper.prepareMainLooper();Looper.prepare(); 方法的区别是直接将 prepare 的入参设为了false。这个入参最终赋给了 new Looper(quitAllowed)。因此接下来看看 Looper 里做了啥:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

看到这里,Looper 里就是为 mQueue 这个变量新建一个 messageQueue,同时根据 threadLocal 每个线程单独存储一份数据的特征,可以得出一个关系:

Thread:Looper:MessageQueue = 1:1:1

也就是 1 个线程对应 11Looper 对应 1MessageQueue

Handler 流程

1. 发送消息

上面讲解了 Handler 的使用,主要是不同线程之间的数据交换,我们从发送数据开始走,察看 Handler 整个流程是怎么样的。发送数据则从 handler.sendMessage(message); 开始看:

   public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
    }

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

  public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

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

  //无论是哪个sendMessage 最后都是调用这个方法
  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

可以发现 Handler 里面发送消息的方法不止几个,但是最后都是调用了 endMessageAtTime(Message msg, long uptimeMillis) 这个方法。这个时候会想 发送消息的方法中的延时以及是在某个时间点发送消息是怎么实现的呢?这个问题放到后面来解答。在 sendMessageAtTime 方法里最后走了 enqueueMessage 方法:

private boolean enqueueMessage(MessageQueue queue,Message msg,
            long uptimeMillis) {
        //msg 与 handler 进行绑定
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        //这里设置同步屏障
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到一开始 MessageHandler 进行了绑定(Message.target 用来与 handler 绑定)。这里最后调用了初始化时设置的 MessageQueue 的 enqueueMessage 方法。接下来进到 MessageQueueenqueueMessage 方法:

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;
            // 注释-1
            if (p == null || when == 0 || when < p.when) { //p是当前messageQueue的首节点
                // 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;
                // 注释-2
                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 (p == null || when == 0 || when < p.when) { //p是当前messageQueue的首节点
       // New head, wake up the event queue if blocked.
       msg.next = p;
       mMessages = msg;
       needWake = mBlocked;
 } 

这里的 p 指的是当前 messageQueue 的首节点,when 指的是调用handler.sendMessage() 时传进来的 msg 要执行的时间节点,p.when 指的是当前 MessageQueue 首节点 p 要执行的时刻。

  • 如果当前队列没有其他需要发送的Message;
  • 或者当前新添加进来的的Message的时间点为0(即需要立即发送的消息);
  • 或者当前新添加进来的的Message需要发送的时间点 < 当前MessageQueue队列头部Message的时间点(即当前添加进来的Message需要在当前MessageQueue队列头部Message之前被发送)时
    就会进入到 if 代码块里面。此时做的事情是将当前新添加的Message插入到了MessageQueue的队首
    接着来看看 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();
     // 注释-2
    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;

走到这一步说明是当前插入的消息需要在队首的消息发送完成之后才有可能发送,而这一步的作用其实就是在 MessageQueue 中找到比插入新消息发送时刻大的节点,将新插入的消息放在该节点之前。
到这里可以看出 MessageQueue 这个链表不是随意排列的,而是根据发送的先后顺序进行排列。越先发送的消息排在越前面。

if (needWake) {
      nativeWake(mPtr);
 }

这里有个标志位 needWake 以及 一个 native 层的方法,用来唤醒消息。到了这个地方就可以解答前面提出的那个问题:Handler 是如何发送延时消息的?
Handler 将要发送的消息按需要执行的时间顺序排列成一个MessageQueue,当队首Message(最近需要发送的Message)未到达发送时间点时,线程被阻塞,所以这里需要根据线程是否阻塞看是否需要唤醒线程,这样才能使新加入的Message能及时发送出去,不会被阻塞。线程的唤醒是通过 native 的方法来实现的。

2. 轮询消息

一开始我们通过 Looper.prepare() 初始化 LooperMessageQueue,然后调用了 Looper.loop() 这个方法。如果我们不调用 Looper.loop() 的话会发现发送了消息后接收不到消息,这里省略了demo。我们猜想在 Looper.loop() 里接收消息:

public static void loop() {
    //省略空判断
     ·····
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
     for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
          msg.target.dispatchMessage(msg);
      }
}

这里省略了一些空判断,然后获取了当前线程的 LooperMessageQueue,接着是一个无限循环的 for 循环。不断调用 MessageQueuenext() 方法得到返回的 Message。也就是通过 next 方法从 MessageQueue 中取出数据:

//MessageQueue.java

Message next() 
       .....//省略一些代码
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
        // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
        // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
        //   如果期间有程序唤醒会立即返回。
        int nextPollTimeoutMillis = 0;
        //next()也是一个无限循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //这里又是一个锁,拿消息和发消息都进行了加锁,保持消息的有序性
            synchronized (this) {
                //获取系统开机到现在的时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages; //当前链表的头结点
                
                //关键!!!
                //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
                if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,
                    //场景如常用的postDelay
                    if (now < msg.when) {
                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取到消息
                        mBlocked = false;
                       //链表操作,获取msg并且删除该节点 
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {
                    //没有消息,nextPollTimeoutMillis复位
                    nextPollTimeoutMillis = -1;
                }
                .....//省略

    }

注意这里有一个 nextPollTimeoutMillis 的变量,发送的延迟或者指定时刻的消息就是根据这个变量来发送消息的,如果到了消息的执行时刻,就会立即返回,否则就一直阻塞,等待到达发送消息的指定时刻:

  • 如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
  • 如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
  • 如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
  • 如果期间有程序唤醒会立即返回。
    最后从 MessageQueue 取到 Msg 返回,由于这里的 Msg 已经和 handler 进行了绑定,也就是 Message.target ,因此最后调用的 msg.target.dispatchMessage(msg); 也就是调用了 handlerdispatchMessage 方法:
public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
  }
======================================================
 public void handleMessage(@NonNull Message msg) {
    }

这里有个 callback,也是包含在 Message 里面,可以通过新建 Handler 时传入(一般没有使用)。一般是走了最后一个回调 handleMessage(msg),也就是平时我们经常重写的那个那个方法,在这里接收消息,(做更新UI的操作)。

Handler传送带模型 Handler 发送接收消息的过程可以看成是一个传送带模型,其中 MessageQueue 为一个链条,承载着一个个 MsgThread 通过给 Looper(滚轮)提供动力,带动这个传送带(MessageQueue)滚动(轮询消息),一般地 handler 在主线程发送消息,然后在 子线程 接收消息,从而达到切换线程的目的。

同步屏障

要先理解两个概念:
同步消息:平时我们一般发送的消息,按执行时间的顺序排列在 MessageQueue
异步消息:需要马上执行的消息,即使插入顺序在队尾仍然需要马上执行

同步屏障的原理就是:
1) 在 MessageQueue 中插入一个屏障
2) 轮询队列中存在异步消息,马上执行异步消息
3) 执行完异步消息后移除同步屏障,接着执行同步消息

一般是系统才需要使用同步屏障(由于刷新界面是必须马上执行消息,否则会出现闪屏等情况),日常开发中一般不需要使用到同步屏障。 同步屏障

Message

Message 获取消息的方式中提供了两种方式:

  1. new Message()
  2. obtain() ······ 一系列方法。
    为了降低内存消耗,可以采取使用 obtain 方法。这里使用了享元 的设计模式,可以好好研究一下。

Q & A

最后这里解答几个高频问题:
1) 一个线程几个 Handler?
一个线程可以有 nhandler,是自己决定的。

2) 一个线程有几个 Looper?如何保证?
一个线程有 1Looper ,通过 threadLocal 线程隔离的特性保证。

3) 为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
因为主线程已经调用了 Looper.prepareMain() 以及 Looper.loop(),已经初始化了 LooperMessageQueue,同时启动了轮询消息的操作,因此在主线程中可以直接 new Handler。因此在子线程中 new Handler 前要调用 Looper.prepare(),之后调用 Looper.loop() 启动轮询。

4)子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
无消息时应该调用 Looper.quit() 方法,执行 quit 后 Handler 机制将失效,执行时如果 MessageQueue 中还有 Message 未执行, 将不会执行未执行 Message,直接退出, 调用quit后将不能发消息给 Handler。

5)既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?
采取的是内存共享的方式,MessageQueue 是一块内存,从子线程发送消息到这块内存,主线程从这块内存获取消息,从而达到切换线程。其内部采取的是加锁的方式,可以参考锁机制(wait,notify····)等知识发散。

6)Looper死循环为什么不会导致应用卡死?
Looper 死循环与应用卡死是两个不相关的关系,APP 整个应用都是由消息组成的,ANR 也是一条消息,当 Looper 收到这条消息后给出一个 ANR 的弹窗,而 Looper 当没有消息时则继续保持休眠,降低能耗,因此Looper 死循环并不是导致应用卡死的原因。

最后给出几篇关于 Handler 的原理的文章:
handler机制的原理
Handler消息机制原理
Handler进阶之sendMessage原理探索
揭秘 Android 消息机制之同步屏障

相关文章

  • 虽迟但到-Handler

    前言 Handler 在 Android 中用到 Handler 的地方非常多,无论是系统线程还是要自己开线程去刷...

  • 曹富贵:虽迟但到

    简书文/ 曹富贵 @曹富贵,代表作《爱与边城》,作家,记者,诗人,编剧,九零后网络写手,自由撰稿人。中国作家协会会...

  • 虽迟但到,Merry Christmas

    Today is Christmas. But honestly, if the party which is s...

  • 毒鸡汤合集8

    毒鸡汤虽迟但到

  • 藤本树短篇集到货记录

    虽迟但到,叔叔家厉害

  • 13虽迟但到的事儿

    我家队友是资深宅男,但某年他主动提出说趁着小朋友还没有到1.2米,赶紧去欢乐谷耍一耍。老父亲主动带娃,定是要支持的...

  • 我的叛逆虽迟但到

    弟弟上了初中后,进入了一个很敏感的阶段——青春期。有时候,看着弟弟顶撞爸妈的样子,我想起自己十几岁的时候。 我的初...

  • 正义虽迟但到,wyf遭刑拘

    说实话,我曾经也是EXO团粉,以及wyf的颜粉,记得最初是在初中的时候,EXO上过一次快乐大本营,我立马就被他的...

  • 虽迟但到的自我检讨

    1月下旬开了一个自我挑战,21天背完BEC中级词汇第一轮,很惭愧没完成第一轮记忆不说,最后一天还因为其他事情造成了...

  • 终于唤醒了你,虽迟但到

    角色归位,遵循各归各的原则 人在求而不得时,就容易生发执念。在父亲刚离世的那段时间里,我迷失了自己。因为与父亲天人...

网友评论

      本文标题:虽迟但到-Handler

      本文链接:https://www.haomeiwen.com/subject/rnyjvktx.html