再看Handler消息传递机制原理

江枫思渺然 提交于 2020-01-22 08:12:56

关于handler实现原理的几个问题 源码分析

1.消息队列的数据结构和实现方式;

2.如何保持线程状态,不被销毁;

3.Message如何优先执行以及确保线程安全;

4.如何直接在主线程中执行

5.关于队列,我常用的实现方式

以下将根据源码来具体谈谈这几个问题,源码使用SDK 28。
实现原理主要包含下面几个类:
MessageQueue:消息队列,在构造方法里初始化Native,持有队列第一个Message,循环遍历队列,队列为 空时阻塞线程。
Handler:发送和处理Message类,包含同步和异步、异步阻塞等结果,获取Message(通过 Message.obtain复用Message)
Message:消息的实体类,包含下一个Message,并有一个静态的Message复用池,还有一些初始化参数等。
Looper:为当前线程创建MessageQueue,并遍历消息队列。

先说一下流程:
1.Looper.prepare()为当前线程创建MessageQueue,Looper.loop()调用MessageQueue.next()方法遍历消息队列 (注:队列为空时next()方法会阻塞);
2.Handler.sendMessage()方法调用MessageQueue.enqueueMessage()方法按照时间顺序向消息队列里面添加Message,如果线程阻塞则唤醒。
3.Looper.loop()遍历消息队列,取出Message,根据Message的target(Handler类型,发送消息时绑定的)调用Handler的dispatchMessage()方法,dispatchMessage()方法会调用handleMessage()方法回调Handler所属的线程处理。(:dispatchMessage()方法会优先调用Message的callback接口(Runnable 类型),其次是Handler的mCallback.handleMessage(),最后是Handler的handleMessage()方法)

注:源码重点部分会加中文注释

1.消息队列的数据结构和实现方式

Message的结构类似于链表,根据每个Message可以获得下一个Message,而MessageQueue持有Message队列的第一个Message.
// Message源码,包含一个Message类型的变量next
   // sometimes we store linked lists of these things
    /*package*/ Message next;

如何保证Message按照时间的顺序执行呢?下面是MessageQueue的enqueueMessage方法,重点代码在我加的中文注释里。

 /**
 *	每一个Message都有一个执行的时刻表,根据时刻表顺序加入到队列
 */
  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;
            // 队列里面的Message为空时表明线程处于需要唤醒的状态,
            // 当时刻表等于0或者时刻表小于队列第一Message的时刻表时,加入到队列最前面
            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;
                  // 把Message按照时间顺序插入到队列
                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) {
            // Native层唤醒阻塞的线程
                nativeWake(mPtr);
            }
        }
        return true;
    }

2.如何保持线程状态,不被销毁

实现的关键就在MessageQueue的next()方法里面。在遍历消息队列的时候阻塞线程。在run()使用Looper.loop()方法之后,后面的run()里面的代码将不再执行

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层获取,如果没有则阻塞
            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.
                        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.
                    // 剩余Message数量为0,修改变量为阻塞,下一次取消息时将在nativePollOnce()方法阻塞
                    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;
        }
    }

3.Message如何优先执行

其实这个已经在第一个问题里面解释了,在加入到消息队列时,遍历消息队列按照时间顺序插入到指定位置。这里面涉及到线程安全问题,即发送消息的线程和Handler本身所属的线程同时操作MessageQueue,请看入队时的这段代码。

 boolean enqueueMessage(Message msg, long when) {
	/..../
		//在入队时同步当前MessageQueue对象。
        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;
            }

           /..../
        }
        return true;
    }

4.如何直接在主线程中执行?

这个问题我们只关注Handler实现的这几个类里面的,其他方式不作探讨。
Handler里面的getMain()方法已经被隐藏了(实际上也是根据Looper来实现的),通过Looper里面有一个静态Looper对象,该对象属于主线程的。然后根据Looper来创建Handler对象,通过Handle的post()方法可直接运行Runnable。这也表明:多个Handler可以共用一个Looper,即能共用消息队列。遍历时只能遍历一个消息队列,即一个Handler有且只能有一个Looper
:在初始化Handler的时候会获取当前线程的Looper,如果为空,则报异常。所以除主线程已经包含Looper之后,其他的线程在创建Handler之前都必须先创建Looper对象(调用Looper.prepare()))

 		Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                // 在这里直接执行
            }
        });

5.关于队列,我常用的实现方式

我之前写队列操作喜欢用java的方式实现。比如用Semaphore的方式来阻塞线程。当队列为空时阻塞,当消息加入时释放Semaphore。见下面代码:对比Handler机指,

public class LightUtil extends Thread
{
    private static final String TAG = LightUtil.class.toString();
    private static LightUtil mInstance;
    private volatile boolean mStart = false;
    ConcurrentLinkedQueue<Message> messages;
    Semaphore semaphore = new Semaphore(0);

    private LightUtil() {
        super("LightUtil");
        messages = new ConcurrentLinkedQueue<Message>();
        mStart = true;
        start();
    }

    public static LightUtil getInstance(){
        if (mInstance == null)
        {
            synchronized (LightUtil.class)
            {
                if (mInstance == null)
                {
                    mInstance = new LightUtil();
                }
            }
        }
        return mInstance;
    }
    
    @Override
    public void run() {
        super.run();
        while (mStart)
        {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                continue;
            }

            Message message = messages.poll();
            while (message != null)
            {
                exeMsg(message);
                message = messages.poll();
            }
        }
    }
    public void addMeesage(Message message){
        messages.add(message);
        semaphore.release();
    }
    public static class Message{
    }
}

Handler的优势:
1.有优先级控制(根据设置的执行时间)
2.可以执行多种类型的任务;

最后:消息队列以及线程池的主要作用就是线程复用。而线程复用的基本实现都是通过阻塞线程,减少线程的创建和销毁(线程创建和销毁开销很大)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!