我来说说Handler 机制及相关问题

从子线程Toast说起?

new Thread(new Runnable() {
            @Override
            public void run() {
                //为每个线程准备一个Looper对象,looper相等于一个循环系统
                Looper.prepare();
                Toast.makeText(MainActivity.this, "测试在子线程中更新ui", Toast.LENGTH_SHORT).show();
//                //这句话的意思是把消息发出去
                Looper.loop();

            }
        }).start();

我们会这样写,现在来究其根本,如果去掉Looper.prepare() 和 Looper.loop()会有什么影响呢?

显然,会报错,报 

Can't toast on a thread that has not called Looper.prepare()"

错误,意思是说toast的需要一个线程的Looper.prepare方法,我们点击toast的show方法,搜索报错文字,会找到以下代码:

if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

然后我们发现Toast的构造方法需要传一个context和looper对象(looper可以传null,后面会系统给其补上),回顾这里的代码,发现如果looper对象为空,会调用Looper.myLooper(), 从ThreadLocal 对象中拿一个looper出来,如果没有拿到,则就会报错,显然,当我们 去掉Looper.prepare() 和 Looper.loop()  的时候会报错,现在分析一下加上Looper.prepare() 为啥就可以了?

Looper.prepare()

 /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

意思就是给当前线程创建一个Looper对象,把线程和Looper对象关联起来,然后调用loop方法进行消息传递,然后调用quit方法停止消息循环。注意,这里创建的looper也是存在ThreadLocal对象中,和刚刚分析的Looper.myLooper()方法对比一下。

Looper.loop()

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
       

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            

            msg.recycleUnchecked();
        }
    }

Looper.loop()方法启动了Message Quene 中的消息队列循环,而且这个循环是死循环,一有消息就会把消息从队列中取出来并发送出去,同时这个方法会检测 ThreadLocal中是否存储了Looper对象,看到这里就会明白之前Toast消息弹出,一定要用Looper.prepare() 和 Looper.loop()进行包裹,并且顺序不能错,否则就会有其他问题出现。

msg.target.dispatchMessage(msg)

注意这里的msg.target 就是 Handler,这个可以在obtain message时看到;

Message 类方法解读

obtain()

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

内部维护了一个消息回收池,在每次回收的时候清除标志位;

MessageQueue 

enqueueMessag(Message msg)

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {    //这里要求消息必须跟 Handler 关联
        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) {    
            //之前是空链表的时候读取消息会阻塞,新添加消息后唤醒
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //插入消息到队列时,只有在队列头部有个屏障并且当前消息是异步的时才需要唤醒队列
            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 queue中使用message 会对其标志位进行检查,并在进入消息队列之后并进行标记。

if (msg.isInUse()) {    
        throw new IllegalStateException(msg + " This message is already in use.");
    }


msg.markInUse()

 next()

这个next方法就是Looper里面在循环的时候调用的,即消息出列,那MessageQueue如何寻找消息呢?

可以看到,MessageQueue.next() 方法里有一个循环,在这个循环中遍历消息链表,找到下一个可以处理的、target 不为空的消息并且执行时间不在未来的消息,就返回,否则就继续往后找。

如果有阻塞(没有消息了或者只有 Delay 的消息),会把 mBlocked这个变量标记为 true,在下一个 Message 进队时会判断这个message 的位置,如果在队首就会调用 nativeWake() 方法唤醒线程!

ThreadLocal 线程本地存储

提供线程内的局部变量,进行线程间的数据进行隔离,数据不共享。具体到ThreadLocal的使用场景,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如说:在网络请求时候,我们需要设置错误码,然后主线程获取,其实我们就可以用一个获取错误码的方式,在进行不同的业务网络请求操作时,我们会赋予不同子线程不同的错误码,并用ThreadLocal存储,这样在取的时候,就不用担心了。

总结:

  1. 消息的表示:Message
  2. 消息队列:MessageQueue
  3. 消息循环,用于循环取出消息进行处理:Looper
  4. 消息处理,消息循环从消息队列中取出消息后要对消息进行处理:Handler
  5. 一个Looper对应一个MessageQueue
  6. 一个线程对应一个Looper
  7. 一个Looper可以对应多个Handler
  8. 线程是默认没有Looper的,线程需要通过Looper.prepare()、绑定Handler到Looper对象、Looper.loop()来建立消息循环

猜你喜欢

转载自blog.csdn.net/sjh_389510506/article/details/88427430