Android源码阅读系列之-Handler

##概述
工作中,我们经常遇到多线程用来处理耗时任务,因为主线程中操作很容易引起ANR。于是在Android中使用Handler进行异步的消息处理,处理完成后将再返回给UI线程执行后续逻辑。

##带着问题去看文章

Q1:子线程可以更新UI么,为什么?

Q2:一个Thread可以有几个Looper?几个Handler?

Q3:可以在子线程直接new一个Handler吗?该怎么做?

Q4:Message的创建方法有几种?哪种效果更好,为什么?

Q5:主线程中Looper的轮询死循环为何没有阻塞主线程?

Q6:使用Hanlder的postDealy()后消息队列会发生什么变化?

Q7:点击页面上的按钮后更新TextView的内容,谈谈你的理解?

Q8:Handler是如何完成子线程和主线程通信的?

Q9:关于ThreadLocal,谈谈你的理解?

Q10:生产者-消费者设计模式?

Q11:享元设计模式?

Q12:Thread两次调用start()方法会发生问题么?

Q13:开启三个线程,循环打印出1到100;
T1:1,4,7.... T2:2,5,8..... T3:3,6,9....

上述这十三个问题,如果你能清晰透彻的了解,那么你大可关闭这篇文章~

##常见用法

//处理
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_TEXT_VIEW:
                    mTextView.setText("UI成功更新");
                default:
                    super.handleMessage(msg);
            }
        }
};

//调用
new Thread(new Runnable() {
            @Override
            public void run() {
				mHandler.obtainMessage(MESSAGE_TEXT_VIEW).sendToTarget();
            }
        }).start();

##Handler的构造方法
我们先来剖析一下Handler的构造方法:

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

    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.myLooper()去获取当前Hander的looper,发现获取不到,于是触发异常:"Can't create handler inside thread that has not called Looper.prepare()",它的意思就是不能在没有得到looper的情况下,就使用handler。但是,我们发现在实际的使用过程中,我们并没有调用Looper.prepare()方法,也没有事,其实是主线程已经帮我们调用,让我们看一下如下的代码:

在ActivityThread中的main方法,它主要做了两个事情:一个准备mainLooper,一个是通过loop()方法创建一个looper:

public static void main(String[] args) {
    ...

    Looper.prepareMainLooper();

    ...
    Looper.loop();

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

在得到looper之后,又通过looper获取了消息队列,这个消息队列其实是一个消息链表,它里面存储的就是Message,即消息。

然后我们再来看一下这个Looper.prepareMainLooper()

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

所以说,ActivityThread在启动的过程中就创建了looper和messageQueue。

##Looper
接下来,我们看一下Looper.prepare()方法里面都做了什么?

static final ThreadLocal sThreadLocal = new ThreadLocal();

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

我们看到,它是在ThreadLocal中存储了对应的Looper对象;我们看到ThreadLocal是被static和final同时修饰,所以,它是不可以被继承的并且全局静态的。所以,它只存在一份。

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

所以,这里我们看到,当我们多次调用prepare的时候,就会报错:"Only one Looper may be created per thread" ,意思是:每一个线程有且只有一个Looper。

那么,这个Looper里面都有什么呢?

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

首先Looper会定义一个消息链表,然后获取Handler当前的线程。
Looper的最主要作用:开启消息循环机制,不断从MessageQueue里面获取Message,然后调用msg.target.dispatchMessage将消息分发出去。

接下来,我们去看一下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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

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

#消息的发送
发送消息,我们调用的是obtainMessage,然后sendtoTarget();在obtainMessage中我们获取的是Message.obtain()方法:

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

这是一个静态的方法,使用了同步锁,获取消息,同时把自己清空掉,放在链表头;然后,sendToTarget负责发送消息,最终调用的是sendMessageAtTime(Message msg, long uptimeMillis)

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

首先,获取消息链表,然后把消息入链表。

##消息的处理

刚才我们说过,我们调用的是msg.target.dispatchMessage,这个msg.target获取的就是handler。然后我们来看一下dispatchMessage都做了什么?

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

这个时候我们可以看到,当我们没有给Message设置callback或者没有设置全局的callback的时候,它会默认的调用handleMessage(msg);这个时候,我们自己的handler就接收到了这个handleMessage。

##问题回顾

###子线程可以更新UI么,为什么?
子线程更新UI要分两种情况,一种是在onCreate里面,一种是在onResume里面。onCreate里面调用没事(如果不sleep()的话),在onResume里面调用就会发生异常:"Only the original thread that created a view hierarchy can touch its views.",根本原因是在Activity初始化的时候,使用WindowManagerGlobal的addView方法,构造出ViewRootImpl,然后使用ViewRootImpl来判断是否在子线程中,而它的创建是在onResume方法里面进行回调的。

###一个Thread可以有几个Looper?几个Handler?
一个Looper,因为调用Looper.prepare()会得到一个looper,多次调用会报错。一个Thread里面可以有多个Handler,通过msg.target进行关联。

###可以在子线程直接new一个Handler吗?该怎么做?
我们正常是不会调用Looper.prepare()方法的,是主线程帮我们调用的。但是假如我们在Thread里面创建了一个Handler的时候,你会发现这个Handler直接在构造函数的时候就会报错。所以解决办法是:在new Handler之前,手动给它创建一个looper,也就是Looper.prepare();

###Message的创建方法有几种?哪种效果更好,为什么?
三种,new Message(), Message.obtain(), mHandler.obtainMessage()
第一种就是直接创建,后两种是从消息链表里面取Message,使用了享元模式。

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

###主线程中Looper的轮询死循环为何没有阻塞主线程?
android的UI线程本质上就是一个轮训机制,你可以理解如果没有这个循环,那么将无法监听用户的时间,无法响应UI。

###使用Hanlder的postDealy()后消息队列会发生什么变化?假如这个时候再来一条延迟消息Handler应该怎么处理?

MessageQueue里面调用nativePollOnce方法来停止阻塞线程,阻塞的时间根据msg.when与now的差值来计算应该阻塞多长时间,如果再来一条非延迟消息,会将当前时间清空,直接执行,如果再来一条延迟消息,会计算当前时间与上一个时间比较,然后执行短的那个。

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 && 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;
        }
        ...
    }
    nextPollTimeoutMillis = 0;

###点击页面上的按钮后更新TextView的内容,谈谈你的理解?

###Handler是如何完成子线程和主线程通信的?
见消息的处理

###关于ThreadLocal,谈谈你的理解?

static final ThreadLocal sThreadLocal = new ThreadLocal();

Thread相当于是简单了实现了一个HashMap,key对应的是每一个线程,value对应它的looper。为啥不用HashMap?因为HashMap里面有很多功能是不需要的,所以单独实现了一个。

###生产者-消费者设计模式?
一个用于生产,一个用于消费,生产者将事件丢进缓冲区,缓冲区满则不继续生产,消费者去消费缓冲区的事件,如果缓冲区空就不消费,并且会通知生产者去生产,同时,生产者生产完成后,要通知消费者去消费。

###Thread两次调用start()方法会发生问题么?

线程首先会运行一次,然后抛出java.lang.IllegalThreadStateException
异常。

if (threadStatus != 0 || started)
            throw new IllegalThreadStateException();

###使用三个线程顺序打印1到100
使用wait和notifyAll,然后利用同步锁volatile将loackNum加锁,利用synchronized对object加锁,实现同步。

static volatile int loackNum = 1;
static Object object = new Object();
public static void main(String args[]){
    final Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object){
                while (loackNum <= 100) {
                    if (loackNum % 3 == 1) {
                        System.out.println(Thread.currentThread().getName() + ":" + loackNum++);
                    }
                    object.notifyAll();
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    });
    Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object){
                while (loackNum <= 100) {
                    if (loackNum % 3 == 2) {
                        System.out.println(Thread.currentThread().getName() + ":" + loackNum++);
                    }
                    object.notifyAll();
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
    Thread thread3 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object){
                while (loackNum <= 100) {
                    if (loackNum % 3 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + loackNum++);
                    }
                    object.notifyAll();
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
    thread1.start();
    thread2.start();
    thread3.start();
}

#总结
Handler负责发送消息和处理消息,Message就是那个消息,通过obtain方法获取消息队列,使用sendMessage将消息入队列。最终通过Looper.loop()方法中的无限循环去dispatchMessage回调到Handler里面。

参考:

https://juejin.im/entry/5708b3c38ac247004c1deef7

https://blog.csdn.net/songzi1228/article/details/82835982