Android消息机制分析(Handler运行机制原理)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37292229/article/details/69230077

Android消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。Handler的运行需要底层的MessageQueue和Looper作为支撑。

一:消息机制简要概括

Handler通过sendMessage()发送Message到MessageQueue队列,通过MessageQueue.enqueueMessage()方法把消息添加到消息队列;Looper通过loop(),无限循环获取消息队列的Message,并将Message交给target来处理;msg.target即Handler,然后调用Handler的dispatchMessage()方法,之后调用Handler的handleMessage()来进行消息处理。

二:概念简要

Handler:主要作用是将一个任务切换到指定线程(即Handler所在的线程)中去执行。比如子线程中获取到数据,到主线程中修改UI;主要用来解决子线程无法访问UI的问题。

MessageQueue:是消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除的工作;

Looper:循环,MessageQueue只是一个消息存储单元,并不能处理消息。所以需要Looper无限循环的形式去查找是否有新消息,然后对信息进行处理;

ThreadLocal:并不是线程,他的作用是在每个线程存储数据。Handler创建的时候会采用当前线程的Looper来构造消息循环系统,获取消息。那么怎么获取当前线程的Looper呢?使用ThreadLocal。ThreadLocal可以在不同线程中互不干扰的存储并提高数据,所以通过ThreadLocal能够轻松的获取每个线程的Looper。注意,线程是没有默认Looper的,如果需要使用Handler就必须为线程创建Looper。主线程即UI线程,也就是ActivityThread,ActivityThread被创建时会初始化Looper,这也就是主线程默认可以使用Handler的原因。

三:Handler工作原理

3.1:sendMessage相关部分源码及解释

通过Handler的sendMessage方法发送一个消息,会先后调用sendMessage —>sendMessageDelayed —>sendMessageAtTime —>enqueueMessage,最好调用MessageQueue的enqueueMessage()方法把这个消息加入到消息队列中

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 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);
    }

  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //注意,在这里把当前Handler赋值给msg.target,之后在loop方法中会用到msg.target;
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

3.2:MessageQueue的enqueueMessage()

通过这个方法把消息添加到消息队列,这里要注意一个方法nativeWake(mPtr),下文中会讲解。

boolean enqueueMessage(Message msg, long when) {
    /...省略代码/
    synchronized (this) {
        /...省略代码/
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //当阻塞时需要唤醒
        } else {
            //将消息按时间顺序插入到MessageQueue。
            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;
            prev.next = msg;
        }
        //消息没有退出,我们认为此时mPtr != 0
        //如果在阻塞中,需要调用nativeWake进行唤醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

3.3:Looper.loop()

获取MessageQueue,然后无限循环读取消息队列的消息,然后执行msg.target.dispatchMessage(msg);进行消息分发。

前文源码Handler的enqueueMessage()中说到:在Handler的enqueueMessage()中,把当前handler赋值给了msg.target;

所以 msg.target.dispatchMessage(msg);即调用Handler.dispatchMessage()方法,然后调用handleMessage方法,我们复写handleMessage自定义实现事件处理。

部分源码:

 public static void loop() {
       /...省略代码/
        final MessageQueue queue = me.mQueue;
        /...省略代码/
        for (;;) {
            Message msg = queue.next(); // might block(可能阻塞)
            if (msg == null) {
                // No message indicates that the message queue is quitting.(没有msg表明消息队列正在退出)
                return;
            }
		  /...省略代码/	
		  //msg.target为handler,调用handler分发msg
                msg.target.dispatchMessage(msg);
          
        }
    }
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Message的next(),这里主要讲解为什么一直处于死循环却不耗费cpu资源,因为调用了nativePollOnce方法。故删除了很多源码


  Message next() {
        final long ptr = mPtr;
		//当消息循环已经退出,则直接返回null,loop的死循环会直接return;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
             //阻塞操作,当消息队列中没有消息返回,则会阻塞;
            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) {
                    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();
						//返回MessageQueue中的下一条即将要执行的消息
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                /...省略代码/
               
            }
             /...省略代码/
        }
    }

主线程的死循环一直运行是不是特别消耗CPU资源呢?

    这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

那什么时候唤醒呢?

上文中我们提到在enqueueMessage()方法中,nativeWake可以唤醒loop;

总结:

Handler通过sendMessage()发送Message到MessageQueue队列【这个时候会调用MessageQueue.enqueueMessage()方法把消息添加到消息队列,如果loop此时处于阻塞状态,则需要调用nativeWake进行唤醒,往消息队列添加Message时,需要根据mBlocked情况来决定是否需要调用nativeWake】;之后Looper通过loop(),无限循环获取消息队列的Message【loop方法,会调用MessageQUeue中的next方法获取message,如果消息队列中没有消息,则会因为nativePollOnce这个方法让loop阻塞】,并将Message交给target来处理;msg.target即Handler,然后调用Handler的dispatchMessage()方法,之后调用Handler的handleMessage()来进行消息处理。

拓展

1.Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper会报错【Can't create handler inside thread that has not called Looper.prepare()")】。主线程也就是ActivityThread,ActivityThread被创建时会初始化Looper;

ActivityThread入口main函数部分源码:


 public static void main(String[] args) {
        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();
 
        //创建ActivityThread对象,建立Binder通道 (创建新线程)
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
		
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        //消息循环开启
        Looper.loop();

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

 

2.主线程中为什么不能执行耗时操作呢?

手机显示的刷新频率是 60Hz,也就是一秒钟刷新 60 次,每 16.67 毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过 16 毫秒。

3.子线程为什么不能访问UI呢?

因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI处于不可预期的状态。为什么不对UI控件添加锁机制呢?原因有二:1.加上锁机制会让UI访问逻辑复杂;2.会降低UI的访问效率,因为锁机制会阻塞某些线程的运行。

子线程访问UI,有checkThread方法,验证当前线程是否主线程,不是会抛出异常。

4.Handler.post()方法

post传递的runnable,然后通过getPostMessage返回一个Message,之后调用sendMessageDelayed。

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }


 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

事件分发的时候,因为在getPostMessage方法中,把runnable赋值给了Message.callback.所以事件分发的时候,会调用handleCallback。之后runnable执行run方法。

   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();
    }

参考文章

1.https://www.zhihu.com/question/34652589

2.http://gityuan.com/2015/12/26/handler-message-framework/

猜你喜欢

转载自blog.csdn.net/weixin_37292229/article/details/69230077