线程间通信:Handler机制

什么是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机制的核心,里面涉及很多特性我们这里都不展开讲述(比如消息屏障机制)。可以扩展阅读深入理解MessageQueue、Handler之同步屏障机制(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机制)