美文网首页
线程间通信:Handler机制

线程间通信:Handler机制

作者: Rave_Tian | 来源:发表于2019-10-27 16:46 被阅读0次

    什么是Handler机制

    Android系统不允许子线程访问UI组件(子线程访问主线程),主要是因为UI控件是非线程安全的,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:

    • 上锁会让UI控件变得复杂和低效
    • 上锁后会阻塞某些进程的执行

    而且Android系统为了避免ANR异常,通常需要新启子线程来处理耗时操作,所以线程间的通信是很常见的开发场景。因此,为了解决子线程更新UI控件以及处理线程间的通信问题,系统提供了Handler机制。总的来说,Handler机制就是跨线程通信的消息传递机制

    简介

    Handler消息机制主要有四个核心类:Message(消息)、MessageQueue(消息队列)、Looper(消息提取泵)、Handler(消息处理者)。它们之间具体协作流程如下:

    Handler运行流程

    message(消息)

    .Message的主要功能是进行消息的封装,同时可以指定消息的操作形式。我们类比生活中的邮寄信件来分析Message,Message对象就是我们要寄出的信件,它应该包括以下内容:

    收件人(消息发送给谁处理):

    • Handler target属性:

    ①通过Message.setTarget(Handler target)来设置,我们常用的Handler.sendMessage(Message msg )发送消息时,在调用Handler类的enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法时,也是设置Message对象的target属性。

        // Handler.java 源码
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    ②在调用Messenger.send(Message message)发送消息时,Messenger对象内部会封装一个Handler对象,这个handler对象就是Message的处理者。

        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Messenger messenger = new Messenger(handler);
        messenger.send(message);
    

    寄件人(对方怎么回信):

    • public Messenger replyTo属性:
      发送消息时设置message.replyTo属性,在消息被接受以后,接收者可以从消息对象的replyTo属性提取出一个Messenger对象。通过这个Messenger对象就可以发送"回信"。当然你也可以发送一个不带寄件人信息的匿名信件,所以replayTo不是必需设定的属性。

    信件内容(要传递的信息或数据)

    • Bundle data属性:通过Bundle来封装需要传递的数据;
    • public Object obj属性:
    • public int arg1属性:如果只需要存储几个整型数据,arg1 和 arg2是setData()的低成本替代品;
    • public int arg2属性:如果只需要存储几个整型数据,arg1 和 arg2是setData()的低成本替代品。

    信件ID(通过不同的ID,接收者做不同的业务处理)

    • public int what属性:在设置what属性的时候,需要注意不同Handler之间what值冲突。

    其他属性

    • Message next属性:用来维护消息在消息队列当中的顺序(参见MessageQueue.enqueueMessage(Message msg, long when) 源码);
    • Runnable callback属性:设置这个属性之后,在消息处理的时候将会拦截Handler.handleMessage(Message msg),转而执行callback的run()方法(参见Handler.dispatchMessage(Message msg)源码);
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
        private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    MessageQueue(消息队列)

    消息队列被封装到Looper里面了,我们一般不会直接与MessageQueue打交道。我们只需要记住它是用来存放消息的单链表结构。队列的顺序由Message的next属性来维护。MessageQueue是整个Handler机制的核心,里面涉及很多特性我们这里都不展开讲述(比如消息屏障机制)。可以扩展阅读深入理解MessageQueueHandler之同步屏障机制(sync barrier)。这里我们只关心队列的入列和出列。

        /**
         * MessageQueue.java
         * 往消息队列添加消息的函数
         * @param msg  待添加的消息
         * @param when uptimeMillis(系统开机运行时间)+delayMillis(延迟时间,由sendEmptyMessageDelayed设置)
         * @return
         */
        boolean enqueueMessage(Message msg, long when) {
            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;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    // 新建队列头,如果队列为空,或者有一个优先级更高的消息插入到队列头部(譬如使用sendMessageAtTime(message,0)),
                    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;
        }
    
        Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                // 这是一个native方法,实际作用就是通过Native层的MessageQueue阻塞nextPollTimeoutMillis毫秒的时间。
                // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
                // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
                // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    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) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            // Message设置的时间为到,将会阻塞等待。
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            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 {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    由上面的代码片段我们可以知道,在使用sendMessageAtTime(Message msg, long uptimeMillis)或者sendMessageDelayed(Message msg, long delayMillis)等方法的时候,我们传入的uptimeMillis和delayMillis并不能准确的设置消息的处理时间。执行的策略是:
    ①优先执行队列中靠前的Message;
    ②如果队列中最考前的Message还没准备好(SystemClock.uptimeMillis() < message.when),此时会阻塞等待。

    Looper(消息提取泵)

    Looper的核心作用就是通过Looper.loop()不断地从MessageQueue中抽取Message,并将消息分发给目标处理者。我们通过介绍Looper运行的三个步骤来掌握Looper的运行原理。

    准备阶段(Looper.prepare())
    我们知道整个消息处理机制的运作,就是将消息添加进消息队列以及从消息队列提前消息进行分发。Looper.prepare()就是用来初始化创建Looper对象和消息列表对象的函数。因此,Looper.prepare()是我们使用Handler机制时必不可少的第一步(注意我们平时在使用主线程的handler时,都是直接new Handler(),具体原因我们稍后分析)。

        /**
         * Looper.java
         */
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    

    由上面的源码可以发现,每个线程只能创建一个Looper(每个Looper又只有一个消息队列),但是消息队列里面可以存放多个Message,而每个Message都可以通过.setTarget(Handler target)来设置自己的Handler处理者。这就是Handler机制中四大要素之间的数理关系。

    • prepareMainLooper():顺带说一下这个方法,它的作用就是在UI线程(主线程)调用Looper.prepare(),并且会保留一个主线程Looper对象的静态引用。这个引用可以通过Looper.getMainLooper()来获取主线程Looper,方便跟子线程跟主线程通信。

    启动和运行Looper.loop()

        /**
         * Looper.java
         */
        public static void loop() {
            // 获取当前线程的Looper对象
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            // 获取当前线程的消息队列
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                // 死循环不断的从消息队列中提取消息
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    // 根据MessageQueue里面的next()方法得知,要使得queue.next()返回null的情况只有两种
                    // 1:MessageQueue.mPtr == 0;这里的mPtr值是调用native方法创建MessageQueue之后,返回的MessageQueue的引用地址,通过这种方式将java层的对象与Native层的对象关联在一起
                    //MessageQueue.mPtr == 0说明消息队列创建失败;
                    // 2:MessageQueue.mQuitting == true;也就是调用Looper的quit()/quitSafely()方法之后,next方法会返回null
                    return;
                }
    
                //.........省略部分代码..........
                try {
                    // 调用Handler的dispatchMessage(Message msg)方法处理消息
                    msg.target.dispatchMessage(msg);
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                //.........省略部分代码..........
    
                msg.recycleUnchecked();
            }
        }
    

    根据源码(配合上面的MessageQueue.next()方法的源码服用),我们可以清晰的看出来Looper.loop()方法的业务逻辑就是一个死循环不停的用MessageQueue.next()方法从消息队列中提取消息,并将消息交给target所持有的Handler进行处理(调用Handler.dispatchMessage(msg)方法)。

    • 因为这里是死循环,所以线程的run()方法中,一般情况下Looper.loop()调用之后的代码逻辑不会被执行到(特殊情况,请参考下面的死循环退出条件)
        private class MyThread extends Thread {
            private Looper subLooper;
            
            @Override
            public void run() {
                Looper.prepare();
                subLooper = Looper.myLooper();
                initHandler();
                Looper.loop();
    
                System.out.println("这句话永远不会被打印,除非消息队列初始化失败或者调用了Looper.quit()");
            }
        }
    
    • 这里的死循环其实是有退出条件的MessageQueue.next()返回null的时候会return,循环终止。循环终止的情况有两种:
      ①MessageQueue.mPtr == 0;这里的mPtr值是调用native方法创建MessageQueue之后,返回的MessageQueue的引用地址,通过这种方式将java层的对象与Native层的对象关联在一起。MessageQueue.mPtr == 0说明消息队列创建失败;
      ②2:MessageQueue.mQuitting == true;也就是调用Looper的quit()/quitSafely()方法之后,next方法会返回null。
    • 如果MessageQueue为空,消息列表没有消息,将会阻塞等待。

    终止和退出quit()/quitSafely()

    相关文章

      网友评论

          本文标题:线程间通信:Handler机制

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