消息机制彻底解析

Posted 胡育诚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消息机制彻底解析相关的知识,希望对你有一定的参考价值。

1 举例子

public class UIActivity extends AppCompatActivity 
    private TextView tv;
    private Handler handler = new Handler()
        @Override
        public void handleMessage(Message msg) 
            switch (msg.what)
                case 0:
                    tv = (TextView) findViewById(R.id.tv);
                    tv.setText((CharSequence) msg.obj);
            
        
    ;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ui);
findViewById(R.id.send_text).setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                new Thread(
                        new Runnable() 
                            @Override
                            public void run() 
                                Message msg = new Message();
                                msg.what = 0 ;
                                msg.obj = "来自另外一个线程的内容";
                                handler.sendMessage(msg);
                            
                        
                ).start();
            
        );
    

2 源码分析

Handler的工作主要包括消息发送和接收过程。消息发送可以通过send和post发送,post方法最终也是通过send方法

1 Handler

    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) 
        msg.target = this;
        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

可以看出,Handler发送消息sendMessage的过程仅仅是向消息队列插入了一条消息

2 Looper

Looper在android的消息机制中扮演消息循环的角色,它会不停地从MessageQueue中查看是否有新消息,如果有就立即处理,没有就一直阻塞在那里

我们知道,Handler的工作需要Looper,主线程默认创建了Looper。但是在子线程需要自己手动创建一个Looper

new Thread("Looper")
    @Override
    public void run()
        Looper.prepare();
        Handler handler=new Handler();
        Looper.loop();
    
.start();

来看看Looper.prepare();

    public static void prepare() 
        prepare(true);
    

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

sThreadLocal.set(new Looper(quitAllowed));使用ThreadLocal在当前线程创建一个Looper

Looper还提供了prepareMainLooper方法,为ActivityThread创建Looper使用,本质也是通过prepare方法实现

public static void prepareMainLooper() 
        prepare(false);
        synchronized (Looper.class) 
            if (sMainLooper != null) 
                throw new IllegalStateException("The main Looper has already been prepared.");
            
            sMainLooper = myLooper();
        
    
public static @Nullable Looper myLooper() 
        return sThreadLocal.get();
    

Looper还提供了一个getMainLooper方法,通过他可以获取到主线程的Looper

public static Looper getMainLooper() 
        synchronized (Looper.class) 
            return sMainLooper;
        
    

再来看看Looper.loop()方法,只有调用了loop方法,消息循环系统才会真正地起作用

public static void loop() 
        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;
        ......
        for (;;) 
            Message msg = queue.next(); // 这里可能阻塞
            if (msg == null) 
                // 跳出死循环的唯一方式是MessageQueue的next方法返回了null
                return;
            
            ......
            //处理消息
            msg.target.dispatchMessage(msg);
            ......
       

next方法在什么时候返回null?
当Looper调用MessageQueue的quit方法通知消息队列退出时,消息队列标记为退出状态,他的next方法就会返回null
消息队列中消息为null时,会进入阻塞状态,此时next并不返回null

再来看看msg.target.dispatchMessage(msg);,msg.target是发送这条消息的Handler对象,即在Looper.loop()所在的线程中调用了Handler的handleMessage,这样就成功将代码逻辑切换到指定线程中执行了

public void dispatchMessage(Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    

3 MessageQueue

MessageQueue是在Looper的构造函数中创建的

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

MessageQueue主要包含两个操作,enqueueMessage,next

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.
                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() 
       ......
        for (;;) 
            ......
                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 
                        // 有消息
                        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 
                    // 没有消息
                    nextPollTimeoutMillis = -1;
                

                // 调用Looper.quit()让MessageQueue进入退出状态
                if (mQuitting) 
                    dispose();
                    return null;
                
                ......
        
    

next是一个无限循环方法,如果消息队列没有方法,next方法就会一直阻塞在这里。当有新消息到来是,next方法会返回这条消息并将其从单链表中移除

3 总结

上面的源码分析大致说明了Handler,Looper,MessageQueue,Message的关系,就拿我们最常见的主线程消息处理过程来说吧

一开始,在主线程通过Looper.prepare()创建了一个和主线程对应的Looper对象(Looper构造函数中会创建一个消息队列MessageQueue),线程和Looper的关系通过ThreadLocal维护

然后调用Looper.loop()进入轮询消息队列的状态,有消息就取消息,没消息就阻塞。

接着我们创建一个Handler对象,并通过Handler发送消息,发送消息的过程就是将消息插入消息队列的过程。

然后Looper轮询发现消息,就会处理该消息,消息的Runnable方法或者Handler的handleMessage方法会在Looper.loop()中调用(Looper是运行在创建Handler所在的线程中,这样Handler的业务逻辑就切换到创建Handler所在的线程中了)

4 技术点分析

1 为什么不允许在子线程访问UI?

因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能导致UI控件处于不可预期的状态。如果对UI控件进行加锁,会让UI访问逻辑变得非常复杂,并且加锁访问会降低UI访问的效率。最简单的方法就是采取单线程模型处理UI。在ViewRootImpl的checkThread方法中,会对UI是否在主线程中进行进行判断,如果在子线程进行,程序抛异常。

2 子线程中Handler发送消息后,消息是怎么传到主线程中的?或者说怎么切换线程的?

我们都知道Handler用来发送消息到消息队列,Looper用来创建消息队列和处理消息。为什么Handler在子线程发送的消息能切换到主线程接收呢?
先来看发送消息

public final boolean sendMessageAtFrontOfQueue(Message msg) 
        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, 0);
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 
        msg.target = this;
        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

这个过程中消息队列从何而来?
在我们创建Handler对象时,就会获取到Looper以及其中的消息队列

public Handler() 
        this(null, false);
    
public Handler(Callback callback, boolean async) 
       ......
        mLooper = Looper.myLooper();
        if (mLooper == null) 
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    

这样,Handler创建时,就获得了创建Handler时所在线程的Looper,就可以在Looper中的消息队列中插入消息,然后Looper一直在轮询,此时有消息了就可以处理消息队列的消息了。这里涉及到了Handler和Looper的一 一对应关系,他们是怎么建立起对应关系的?

3 Handler和Looper是怎么建立一一对应关系的?

为什么Handler可以准确无误的获取到Handler被创建的所在线程的Looper?

public Handler(Callback callback, boolean async) 
       ......
        mLooper = Looper.myLooper();
        if (mLooper == null) 
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    
public static @Nullable Looper myLooper() 
        return sThreadLocal.get();
    
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() 
        prepare(true);
    

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

答案是ThreadLocal
ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当某些数据是以线程为作用域并且不同的线程具有不同的数据副本的时候,可以考虑使用ThreadLocal。很显然Looper的作用域就是线程并且不同线程具有不同Looper。如果不使用ThreadLocal,系统就必须提供一个全局的哈希表。
ThreadLocal篇

4 Looper是通过handler的方法handleMessage处理消息的,handler对象是如何传递到Looper的?

handler在发送消息时,消息会持有handler的引用,然后消息插入到消息队列

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 
        msg.target = this;
        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

Looper从消息队列中取出消息,就可以获取到该消息相应的handler,就可以调用msg.target.dispatchMessage(msg);处理方法,最终调用handleMessage()

public static void loop() 
        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;
        ......
        for (;;) 
            Message msg = queue.next(); // might block
            if (msg == null) 
                // No message indicates that the message queue is quitting.
                return;
            
            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);
        
    

5 有消息Looper处理消息,没消息Looper一直阻塞(轮询),怎么退出Looper轮询?或者说怎么停止有Looper的子线程?

Looper提供了quit和quitSafely来退出一个Looper。
quit会直接退出Looper。
quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。
当Looper调用quit时,MessageQueue的quit或者quitSafely会调用来通知消息队列退出,当消息队列标记为退出状态时,他的next会返回null,此时轮询会退出

Message next() 
        ......
        for (;;) 
            ......
                if (mQuitting) 
                    dispose();
                    return null;
                
            ......
        
    
public static void loop() 
        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;
        ......
        for (;;) 
            Message msg = queue.next(); // might block
            if (msg == null) 
                // No message indicates that the message queue is quitting.
                return;
            
            ......
            msg.target.dispatchMessage(msg);
        
    

Looper退出后,通过Handler发送的消息会失败,此时Handler的send方法会返回false

boolean enqueueMessage(Message msg, long when) 
        ......
        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;
    

6 Handler中的post(Runnable r)方法

Looper处理消息是通过Handler的dispatchMessage方法

public void dispatchMessage(Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    

首先检查msg.callback是否为空,msg.callback是Runnable对象,实际上就是Handler的post方法传递的Runnable参数。

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;
    

Handler的post方法最终都是调用Handler的send方法
再来看看HandleCallback,直接调用message.callback(Runnable)的run方法,这里的run方法在Looper所在线程运行

private static void handleCallback(Message message) 
        message.callback.run();
    

7 Handler handler=new Handler(callback)

Handler的dispatchMessage方法中除了检查msg.callback是否为null,还检查mCallback是否为null,Handler有个构造函数可以传递Callback进去,Handler handler=new Handler(callback);
我们通常创建一个Handler的子类并重写handleMessage()方法,Callback提供了另一种创建Handler的方式,我们不想写子类时,可以通过Callback来实现

8 Handler handler=new Handler(looper)

通过Handler handler=new Handler(),我们获取到的是当前线程的Looper,即Handler handler=new Handler()所在线程的Looper

public Handler() 
        this(null, false);
    
public Handler(Callback callback, boolean async) 
        ......
        mLooper = Looper.myLooper();
        if (mLooper == null) 
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    

而我们传入一个looper,消息是在looper所在线程处理,Handler handler=new Handler(looper)所在线程可能和looper所在线程不一样

消息处理的线程就是Looper.loop()所在的线程

public Handler(Looper looper) 
        this(looper, null, false);
    
public Handler(Looper looper, Callback callback, boolean async) 
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    

new Handler()和new Handler(Looper.getMainLooper())的区别

如果你不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper
要刷新UI,handler要用到主线程的looper。那么在主线程 Handler handler = new Handler();,如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());

以上是关于消息机制彻底解析的主要内容,如果未能解决你的问题,请参考以下文章

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

彻底解析Android缓存机制——LruCache

彻底解析Android缓存机制——LruCache

Android AsyncTask完全解析,带你从源码的角度彻底理解

JVM 类加载机制全面解析,一篇完整彻底搞懂

JVM 类加载机制全面解析,一篇完整彻底搞懂