Handler、MessageQueue、Looper、Thread的关系

后端存储 简书

问题:

1:为什么在子线程中需要维护Looper?

2:Handler、MessageQueue、Looper、Thread相互有什么依赖关系?

3:为什么用ThreadLocal来设置Looper和Thread的对应关系?

为什么在主线程中可以直接New Handler,而在子线程中需要Looper.prepare() ?

首先,主线程中不是Handler不需要维护Looper,而是Android系统在ActivityThread中已经为开发者维护了一个Looper,在程序入口ActivityThread的main方法里面我们可以找到:

android.app.ActivityThread.java

public static void main(String[] args) {
...
        Looper.prepareMainLooper();
...
        Looper.loop();
...
    }

我们从最开始的Looper进行分析:

android.os.Looper.java

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

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

  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;
        //确保此线程的标识是本地进程的标识,并跟踪该标识实际上是什么。
        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.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
            //这里的target是发送消息的Handler
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

这里sThreadLocal.get()这个方法获取的是当前的线程所对应的线程局部变量,在ThreadLocal类中有一个ThreadLocalMap,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

而sThreadLocal.set(Looper)用于设置当前线程的线程局部变量的值。所以Thread与Looper是在ThreadLocalMap以键值对的方式一一对应的。

当Looper执行prepare向ThreadLocalMap设置value时,会在创建Looper的同时创建一个消息队列MessageQueue。而Looper的loop()方法其实就是一个死循环中MessageQueue.next()不断从消息队列拿消息的过程。

那么Handler呢?

android.os.Handler.java

public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
       //获取当前的loop,如果为空就抛出异常
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
       //消息队列 ,其实是一个链表,是从前面获取的mLooper中获取的
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

  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) {
        //enqueueMessage中首先为meg.target赋值为this,如果大家还记得Looper的loop方法会取出每个msg然后交给
        //msg,target.dispatchMessage(msg)去处理消息,也就是把当前的handler作为msg的target属性。最终会调用queue的
        //enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

总结一下

1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

2、Looper.loop()会让当前线程进入一个死循环,不断从MessageQueue的实例中读取消息,然后调用msg.target.despatchMess-

age(msg)方法。

3、Handler的构造方法,会得到当前线程中保存的Looper对象,与Looper实例中的MessageQueue相关联。

4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。

5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

最后,为什么用ThreadLocal来设置Looper和Thread的对应关系呢?

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

简书稿源:简书 (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » Handler、MessageQueue、Looper、Thread的关系

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录