Android进阶-Handler源码解析

Handler源码解析

    • Handler的简单使用
      • post()
      • sendMessage()
    • 1.创建Handler
      • Handler handler = new Handler();
    • 2. 发送消息
      • mHandler.sendMessage(message);
      • enqueueMessage(Message msg, long when)
      • next()
    • 3.处理消息
      • 1)Looper的创建及循环机制
        • ①主线程Looper建立
        • ② Looper的消息循环
      • 2)dispatchMessage(Message msg)
    • 4.ThreadLocal
    • 5.疑问解答
      • Handler.sendMessageDelayed()怎么实现延迟的?
      • Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?
      • 为什么子线程不能更新UI?
    • Message的创建方式
    • Linux pipe/epoll机制:
    • 致谢

Android进阶-Handler源码解析_第1张图片

Handler的简单使用

post()

final Handler handler = new Handler();

new Thread(new Runnable() {
   @Override
   public void run() {
       /**
          耗时操作
        */
      handler.post(new Runnable() {
          @Override
          public void run() {
              /**
                更新UI
               */
          }
      });
   }
 }).start();

sendMessage()

//1,创建Handler(主线程)
private Handler handler = new Handler() {
     @Override
     //3,处理消息
     public void handleMessage(@NonNull Message msg) {
         super.handleMessage(msg);
         switch (msg.what) {
             case 1:
                 Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_LONG).show();
                 break;
         }
     }
//2,发送消息(子线程)
new Thread(new Runnable() {
        @Override
        public void run() {
            SystemClock.sleep(2000);
            Message msg = new Message();
            msg.what = 1;
            msg.obj = "我是子线程发来的消息";
            handler.sendMessage(msg);
        }
    }).start();

Android进阶-Handler源码解析_第2张图片

1.创建Handler

Handler handler = new Handler();

【 Handler.java】

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    /**
     * Default constructor associates this handler with the queue for the
     * current thread.
     *
     * If there isn't one, this handler won't be able to receive messages.
     */
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> 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();//核心代码。获取一个Looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//核心代码。从Looper获取一个消息队列
        mCallback = null;
    }

在源码中,Handler定义了一个Looper对象mLooper和一个MessageQueue消息队列mQueue,并都进行了初始化,分别对mQueue和mLooper进行了赋值,其中mLooper是通过Looper.myLooper()赋值,mQueues是Looper中的mQueue。

让我们进入Looper类看看
【Looper.java】

public class Looper {
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
    volatile boolean mRun;

    private Printer mLogging;

    
    public static void prepare() {
        prepare(true);
    }
    
	private Looper(boolean quitAllowed) {
	       mQueue = new MessageQueue(quitAllowed);
	       mRun = true;
	       mThread = Thread.currentThread();
	   }

    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));//设置myLooper
    }

    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 void loop() {
       ......
    }
    
    public static Looper myLooper() {
        return sThreadLocal.get();
    }
   ......
}

有两个方法prepare()和prepareMainLooper()对Looper进行了初始化。prepare()创建的Looper为允许退出循环的,而prepareMainLooper()方法创建的是不应许退出循环的,通过分析,很明显知道prepare()方法创建的是一般线程的Looper,而通过而prepareMainLooper()创建的,就是主线程消息循环的Looper。

2. 发送消息

mHandler.sendMessage(message);

【Handler.java】

    ......
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) { //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);
    }
....

我们代码中调用sendMessage()方法,最终就是走的sendMessageAtTime()方法。而且其他发送消息的方法除了sendMessageAtFrontOfQueue(),例如sendMessageDelayed(),sendEmptyMessageDelayed()最终都会走sendMessageAtTime()方法,这个方法主要是赋值MessageQueue,调用enqueueMessage方法

Handler发送主体为Message,Message是啥呢?Message主要就是对一些数据做封装处理,其中有int变量what,arg1,arg2,Object变量obj等,具体可以查看Message源码,这里就不详细说了。
【Handler.java】

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //将当前的Handler绑定给msg.target
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

enqueueMessage()方法中,首先将当前的Handler绑定给msg.target,接着调用MessageQueue的enqueueMessage()方法,MessageQueue的enqueueMessage()方法则是消息入队的方法。

enqueueMessage(Message msg, long when)

【MessageQueue.java】

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {//msg.target就是发送此消息的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;//将延迟时间封装到msg内部
            Message p = mMessages;//消息队列的第一个元素
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
               //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
          
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程。
  
                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;
    }

MessageQueue中enqueueMessage方法的目的有两个:

  • 1.插入消息到消息队列
  • 2.唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态)
    同时我们知道了MessageQueue的底层数据结构是单向链表,MessageQueue中的成员变量mMessages指向的就是该链表的头部元素。

next()

【MessageQueue.java】

Message next() {
    
        final long ptr = mPtr;
        if (ptr == 0) {
           //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
           //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
           //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
           //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
           
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
                    //如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //正常取出消息
                        //设置mBlocked = false代表目前没有阻塞
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //没有消息,会一直阻塞,直到被唤醒
                    nextPollTimeoutMillis = -1;
                }

                if (mQuitting) {
                    dispose();
                    return null;
                }

            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

由此可以看出:

  • 1.当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。
  • 2.读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。
  • 3.如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒
  • 4.如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。

通过enqueueMessage和next两个方法的分析我们不难得出:

  • 消息的入列和出列是一个生产-消费者模式,Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作。

3.处理消息

1)Looper的创建及循环机制

上面说到,Looper的建立有两种方式prepare()和prepareMainLooper(),其中prepare建立的为一般子线程Looper,可以取消循环;而prepareMainLooper()建立的为主线程的Looper,不可以取消循环。到底而prepareMainLooper建立的是不是主线程循环呢?让我们继续分析

①主线程Looper建立

主线程即UI线程,说到UI线程,我们知道应用程序一启动,主(UI)线程就开始启动,而线程的建立必须要在进程的基础上。**通过对Android应用程序启动的分析,我们知道,应用程序启动,首先会通过Zygote复制自身fork出一个进程,然后再由进程创建一个主线程,主线程的建立和ActivityThread息息相关,通过分析,知ActivityThread的main方法就是应用程序启动的入口。
让我们来看一下ActivityThread类的main方法:

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

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Process.setArgV0("");

        Looper.prepareMainLooper();//1.主线程Looper创建
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();//2.主线程Looper循环

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

从源码知道,正如我们想的那样prepareMainLooper()建立的Looper就是主线程的Looper。

② Looper的消息循环

从上面ActivityThread的main方法中,我们发现Looper.loop()消息循环方法。Looper是怎么循环的,这里让我们来看一下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;

        // 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 (;;) {//for循环
            Message msg = queue.next(); //从消息队列中取值
            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.recycle();
        }
    }

代码第6行,从轮询器中获取消息队列。接着通过一个死循环来把消息队列中的消息逐个取出来。代码第18行,通过MessageQueue的next()方法取出消息,当queue.next返回null时会退出消息循环。有消息则调用msg.target.dispatchMessage(msg),target就是发送message时跟message关联的handler,Message被处理后会被调用recycleUnchecked()进行回收。

2)dispatchMessage(Message msg)

【Handler.java】

public void dispatchMessage(Message msg) {

//若msg.callback属性不为空,则代表使用了post(Runnable r)方式发送消息;则执行handleCallback(msg)()->> 分析2
   if (msg.callback != null) {
       handleCallback(msg);
   } else {
       if (mCallback != null) {
           if (mCallback.handleMessage(msg)) {
               return;
           }
       }

       //若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则执行handleMessage(msg),即回调复写的handleMessage(msg) 
       handleMessage(msg);
   }
}

private static void handleCallback(Message message) {
	//  Message对象的callback属性 = 传入的Runnable对象; 即回调post(Runnable r),Runnable对象里复写的run()
        message.callback.run();
    }

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

4.ThreadLocal

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。 一般来说,当某些数据是以线程为 作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

对于Handler来说, 它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。

实际的例子来演示ThreadLocal的真正含义。首先定义一个ThreadLocal对象,这里选择Boolean类型的,如下所示。

private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();

然后分别在主线程、子线程1和子线程2中设置和访问它的值,代码如下所示。

 mBooleanThreadLocal.set(true);
 Log.d(TAG,"[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); //输出 true
 new Thread("Thread#1") {
        @Override
        public void run() {
            mBooleanThreadLocal.set(false);
            Log.d(TAG,"[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); //输出 flase
        };
    }.start();
 new Thread("Thread#2") {
        @Override
        public void run() {
            Log.d(TAG,"[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); // 由于没有设置值,所以应该是null
        };
    }.start();

ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数
据的副本并且彼此互不干扰。

5.疑问解答

Handler.sendMessageDelayed()怎么实现延迟的?

前面我们分析了如果拿到的消息还没有到时间,则会重新设置超时时间并赋值给nextPollTimeoutMillis,然后调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,这是一个本地方法,会调用底层C++代码,C++代码最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟的。

Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

首先我们来看造成ANR的原因:

  • 1.当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
  • 2.当前的事件正在处理,但没有及时完成

我们再来看一下APP的入口ActivityThread的main方法:

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

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

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

显而易见的,如果main方法中没有looper进行死循环,那么主线程一运行完毕就会退出,会导致直接崩溃,还玩什么!

现在我们知道了消息循环的必要性,那为什么这个死循环不会造成ANR异常呢?

我们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它,这也就是我们为什么不能在UI线程中处理耗时操作的原因。
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,唤醒主线程,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

为什么子线程不能更新UI?

android系统当中的控件都是线程不安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。

Message的创建方式

//直接new 不推荐
1.Message message = new Message();
 
//从全局消息池取出一个空闲Message供开发者使用,推荐
2.Message message = Message.obtain();
 
//从全局消息池获取,与handler关联,最终会调用2方法推荐使用
3.Message message = handler.obtainMessage();

Linux pipe/epoll机制:

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

致谢

深入理解MessageQueue

关于Handler同步屏障你可能不知道的问题

Handler发送消息的方式

都 2021 年了,还有人在研究 Handler

你可能感兴趣的:(Android进阶,android)