[Android 消息机制]—— Handler 机制详解

Android 消息机制 —— Handler

概述

Android 的消息机制主要指的是 Handler 的运行机制,从开发者的角度来讲 Handler 是 Android 消息机制的上层接口,而底层的逻辑则是由 MessageQueue、 Looper 来完成的。java

Handler 的设计目的是为了解决不能在 Android 主线程中作耗时操做而又只有主线程才能访问 UI 的矛盾。经过 Handler 消息机制可让开发者在子线程中完成耗时操做的同时在主线程中更新UI。android

这里要思考一个问题:为何 Android 非要规定只有主线程才能更新 UI 呢?算法

由于 Android 的全部 View 控件都不是线程安全的,若是在多线程中并发访问极可能形成意想不到的结果。对于加锁这种方案也不可取,首先加锁以后会让 UI 访问逻辑变的很复杂,开发者须要时刻考虑多线程并发将会带来的问题,其次锁机制过重了它会严重影响 UI 访问效率。介于这两个缺点,最简单且高效的方法就是采用单线程的方式访问 UI。Handler 机制应运而生。shell

那么 Handler 内部是如何完成线程切换的呢?答案就是神奇的 :ThreadLocal安全

ThreadLocal

ThreadLocal 并非 Thread ,他的特色颇有意思: 每个线程存储的值是相互隔离的数据结构

public class TreadLocalDemo {
    // 就算设置为 static 结果也是同样的
    ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();

    public void runDemo() {
        mThreadLocal.set(true);
        System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get());
        new Thread("Thread#1") {
            @Override
            public void run() {
                super.run();
                mThreadLocal.set(false);
                System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                super.run();
                System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get());
            }
        }.start();
        System.out.println(Thread.currentThread().getName() + " " + mThreadLocal.get());
    }
}
复制代码

运行的结果很清晰的展现他的特色,虽然在主线程和线程1中都作了赋值操做,但并不能改变原来线程的赋值状况。多线程

\[外链图片转存失败(img-B6mQnGGd-1567932976362)(assets/image-20190905231656636.png)\]

对于 ThreadLocal 的原理简单来讲:每一线程都有一个专门用于保存 ThreadLocal 的成员变量 localValues。 尽管在不一样线程中访问同一个 ThreadLocal 的 setget 方法,但所作的操做都仅限制于各自线程的内部。这就是 ThreadLocal 能够在多个线程中互不干扰的存储和读取数据的缘由。正是这种特性让 Handler 作到了线程的切换。并发

Looper 正是借助 ThreadLocal 的特色在不一样的线程建立不一样的实例。至此 Handler 与 Looper 、线程达到了一一对应的绑定关系。因此不管此 Handler 的实例在什么线程调用,最终的回调都会分发到建立线程。app

MessageQueue

MessageQueue 主要有两个操做:插入和读取。读取操做也会伴随着删除,插入和读取的方法分别对应的是:enquequeMessagenext,MessageQueue 并非像名字同样使用队列做为数据结构,而是使用单链表来维护消息。单链表在插入和删除上比较有优点。ide

next()

首先来讲说 next 方法。

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            // 可见只有在调用 quit() 方法以后才会返回空
            return null;
        } 
        
   ......
          
        // 一个死循环 !
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 一个 native 方法,此方法在没有消息或者消息没有到执行时间的时候会让线程进入等待状态。
          	// 有点相似于 Object.wait 可是 nativePollOnce 能够自定等待时间
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
   ......
       				 if (!keep) {
                    synchronized (this) {
                      // 获取消息后从列表中移除
                        mIdleHandlers.remove(idler);
                    }
                }
        }
    }
复制代码

最关键的是三点内容

  1. 死循环
  2. nativePollOnce()
  3. 获取到消息以后从列表中移除

nativePollOnce 是一个 native 方法,若是单列表中没有消息或者等待的时间没有到,那么当前线程将会被设置为 **wait 等待状态 **,直到能够获取到下一个 Message 线程更详细的内容能够参见 StackOverflow 上关于 nativePollOnce的回答而这个死循环的目的就是不让 next方法退出,等待 nativePollOnce 的响应。等到获取到消息以后再将这个消息从消息列表中移除。

enqueueMessage()

enqueueMessage 方法的主要工做就是向单链表中插入数据,当线程处于等待状态则调用 nativeWake 唤醒线程,让 next 方法处理消息。

boolean enqueueMessage(Message msg, long when) {
    ......
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

如何处理延时消息

详情请参见Handler是怎么作到消息延时发送的 下面再抄一部分结论:

在 next 方法中若是头部的这个 Message 是有延迟并且延迟时间没到的(now < msg.when),会计算一下时间(保存为变量 nextPollTimeoutMillis), 而后在循环开始的时候判断若是这个 Message 有延迟,就调用nativePollOnce (ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的做用相似与 Object.wait(), 只不过是使用了 Native 的方法对这个线程精确时间的唤醒。

  1. postDelay()一个10秒钟的 Runnable A、消息进队,MessageQueue 调用nativePollOnce()阻塞,Looper 阻塞;
  2. 紧接着 post() 一个 Runnable B、消息进队,判断如今A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),而后调用 nativeWake()方法唤醒线程;
  3. MessageQueue.next() 方法被唤醒后,从新开始读取消息链表,第一个消息B无延时,直接返回给 Looper
  4. Looper 处理完这个消息再次调用 next() 方法,MessageQueue 继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩 9秒)继续调用 nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message 进队;

\[外链图片转存失败(img-xbx9xlR5-1567932976364)(assets/image-20190908155849230.png)\]

Looper

Looper 在 Android 消息机制中扮演着消息循环的角色。具体来讲他的任务就是不停的从 MessageQueue 中获取消息,若是有新消息就当即处理,没有消息的时候,与 Looper 绑定的线程就会被 MessageQueue 的 next 的 nativePollOne 方法置于等待状态。

Looper 是如何建立的

/** 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");
    }
    // 建立 Looper 实例,将实例保存在 sThreadLocal 中与当前线程绑定。
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

在构造方法里面他会建立一个 MessageQueque,并保存当前线程。

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

getMainLooper 能够在任何地方获取到主线程的 Looper,那么主线程是如何建立 Looper 的呢?

主线程建立 Looper 的过程 —— AndroidThread

咱们的目光来到了 AndroidThread 类, 在 AndroidThread 中咱们看到了熟悉的方法 :main(String[] args。千万不要被 AndroidThread 的名字所迷惑,AndroidThread 并非一个线程,它只是一个开启主线程的类。

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

        // 建立 Looper 和 MessageQueue 对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        // 建立 ActivityThread 对象
        ActivityThread thread = new ActivityThread(); 

        // 创建 Binder 通道 (建立新线程)
        thread.attach(false);

 			 // 消息循环运行
        Looper.loop(); 
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
复制代码

注意调用的是 prepare(false) 不容许退出,这是为何呢?

public static void prepareMainLooper() {
       // 不容许退出
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
复制代码

这是由于主线程的 Looper 伴随着一个 App 的整个生命周期,全部的 UI访问、View 刷新都是在 Looper 里面完成的,若是容许开发者手动退出,那么整个 App 都会变得不可控。

更多细节能够参见下面的一节「 Looper中的死循环为何没有卡死线程」

Looper 是如何运行的

/** * 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();
  ......
    
    final MessageQueue queue = me.mQueue;
  ......

    // 死循环
    for (;;) {
       // 可能会被阻塞
        Message msg = queue.next();
        if (msg == null) {
            // msg 为 null 会当即退出循环,这也是退出循环的惟一方法。
            return;
        }
   ......
  
        try {
          // 开始分发消息
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
   ......
    }
}
复制代码

loop 方法是一个死循环,他的工做就是不断的检查 MessageQueue 是否有能够处理的消息,若是有这将消息分发给 Handler 处理。既然是死循环那么为何没有卡死线程呢?更多细节能够参见下面的一节「 Looper中的死循环为何没有卡死线程」

Looper 如何退出

Looper 内部提供了两种退出的方法,分别是 quit、quitSafely。从本质上来说 quit 调用后会当即退出 Looper,而 quitSafely 只是设定一个退出标记,等待消息队列中的已有消息处理完毕后,再退出。

Looper 退出后,经过 Handler 发送的消息会失败,这个时候 Handler send 方法会返回 false。在子线程中,若是手动为其建立了 Looper,那么在全部的逻辑完成后理应手动调用 quit 方法终止 Looper 内部的循环,不然这个子线程会一直处于等待状态,而退出 Looper 以后,子线程也就会随之终止,所以在子线程中使用 Looper,必须在恰当的时机终止它

/** * Quits the looper. */
public void quit() {
    mQueue.quit(false);
}

/** * Quits the looper safely. */
public void quitSafely() {
    mQueue.quit(true);
}
复制代码

若是是主线程开发者就退出不了,要是退出了,就麻烦大了。

public static void prepareMainLooper() {
  // fasle 不容许退出
    prepare(false);
 ....
}
复制代码

退出的本质

Looper.quit 的源码中能够清晰看到,本质上调用的是 MessageQueue 的 quite 方法。而在调用 MessageQueue.quite 以后 再次调用 MessageQueue.next()会返回 null

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            // 可见只有在调用 quit() 方法以后才会返回空
            return null;
        } 
        
   ......
复制代码

Looper.loop()在调用 queue.next()得的结果为 null 的时候会当即跳出死循环, 这也是退出死循环的惟一方式。

public static void loop() {
……
	for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
……
复制代码

Looper中的死循环为何没有卡死线程

参考知乎:Android中为何主线程不会由于Looper.loop()里的死循环卡死?

咱们都知道:一个简单的死循环会消耗掉大量资源致使线程被卡死。可是 Looper.loop() 方法开启就是一个死循环,它为何没有卡死线程呢?总结一下主要有3个疑惑:

  • Looper 为何要使用死循环
  • Android 的主线程为何没有被 Looper 中的死循环卡死
  • 唤醒 Looper 的消息从何而来

Looper 为何要使用死循环

首先要说说的是为何在 Looper 中使用死循环。在 CPU 看来操做系统线程(这里的定义能够参见《Java基础》多线程和线程同步 —— 进程与线程一节 ) 只不过是一段能够执行的代码,CPU 会使用 CFS 调度算法,保证每个 task 都尽量公平的享用 CPU 时间片。既然操做系统线程是一段能够执行的代码,当可执行的代码结束以后,线程生命周期也就终止,线程将会退出。可是对于 Android 主线程这种场景,咱们绝对不但愿执行一段时间以后主线程就本身中止掉,那么如何保证线程一直执行下去呢?简单的作法就是在线程中执行死循环,让线程一直工做下去不会中止退出。

总的来讲,在线程中使用死循环想要解决的问题就是防止线程本身退出。因此对于 Looper 而言,他的死循环就是但愿不断的从 MessageQueue 中获取消息,而不但愿线程线性执行以后就退出。

Android 的主线程为何没有被 Looper 中的死循环卡死

首先 Android 全部的 UI 刷新和生命周期的回调都是由 Handler消息机制完成的,就是说 UI 刷新和生命周期的回调都是依赖 Looper 里面的死循环完成的,这样设计的目的上文已经阐述清楚。这篇文章里面贴了 AndroidTread 对于 Handler 的实现类 H 的源码(进入文章后搜索:内部类H的部分源码) 源码太长,我就不贴了。

其次Looper 不是一直拼命干活的傻小子,而是一个有活就干没活睡觉的老司机,因此主线程的死循环并非一直占据着 CPU 的资源不释放,不会形成过分消耗资源的问题。这里涉及到了Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便在 loop 的 queue.next() 中的 nativePollOnce() 方法里让线程进入休眠状态,此时主线程会释放CPU资源,直到下个消息到达或者有事务发生才会再次被唤醒。因此 Looper 里的死循环,没有一直空轮询着瞎忙,也不是进入阻塞状态占据着 CPU 不释放,而是进入了会释放资源的等待状态,等待着被唤醒

通过上面的讨论能够得知:

  1. Looper 中的死循环是 Android 主线程刷新 UI 和生命周期回调的基石。
  2. Looper 中的死循环会根据消息分别进入等待和唤醒状态,并不会一直持有资源,因此就不会有卡死的问题。

那么唤醒 Looper 的消息是从哪里来的呢?

唤醒 Looper 的消息从何而来

目光回到 AndroidThread 类中的这几行代码

public static void main(String[] args) {
        ....
          
        // 建立ActivityThread对象
        ActivityThread thread = new ActivityThread(); 
  
        //创建Binder通道 (建立新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
    }
复制代码

在建立 ActivityThread 后会经过thread.attach(false)方法在 ActivityThread 中建立 Binder 的服务端用于接收系统服务AMS发送来的事件,而后经过 ActivityThread 的内部类 ApplicationThread 中 sendMessage 方法

......

public final void scheduleStopActivity(IBinder token, boolean showWindow, int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

        public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
            sendMessage(
                showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
                token);
        }

        public final void scheduleSleeping(IBinder token, boolean sleeping) {
            sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
        }

        public final void scheduleResumeActivity(IBinder token, int processState, boolean isForward, Bundle resumeArgs) {
            updateProcessState(processState, false);
            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
......

复制代码

将消息发送给 AndroidThread 的 Handler 实现内部类 H。从而完成了ActivityThread 到 UI 线程即主线程的切换,唤醒 Looper 进行 dispatchMessage 的动做。

唤醒的具体操做参见上文「MessageQueue -> enqueueMessage -> nativeWake」

拓展:如何在非主线程中使用 Handler 消息机制

经过 ActivityThread 的源码能够清楚看到

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

        //建立Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();
        ....

        Looper.loop(); //消息循环运行
        ....
    }
复制代码

Android 在没启动一个 App 的时候都会建立一个 Looper,而开始子线程的时候是没有这个操做的,因此须要开发者本身建立并调用 Looper.loop() 让 Looper 运行起来。

new Thread("Thread#1") {
      @Override
      public void run() {
         // 手动生成为当前线程生成 Looper
         Looper.prepare();
         Handler handler = new Handler();
         Looper.loop();
      }

    }.start();
复制代码

此处咱们作个实验,既然 Looper 是个死循环那么在 loop() 以后的代码是否是永远没有机会执行呢?

/** * Android 消息机制 —— Handler * <p> * Created by im_dsd on 2019-09-07 */
public class HandlerDemo {

    public static final String TAG = "HandlerDemo";
    private Handler mHandler;
    private Looper mLooper;

    /** * 如何在子线程中开启 Handler */
    public void startThreadHandler() {
        new Thread("Thread#1") {
            @Override
            public void run() {
                // 手动生成为当前线程生成 Looper
                Looper.prepare();
                mLooper = Looper.myLooper();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d(TAG,Thread.currentThread().getName() + " " + msg.what);
                    }
                };
                Log.d(TAG,Thread.currentThread().getName() + "loop 开始 会执行吗? ");
                // 手动开启循环
                Looper.loop();
                Log.d(TAG,Thread.currentThread().getName() + "loop 结束 会执行吗? ");
            }

        }.start();

        // 等待线程启动
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG,"start send message");
        mHandler.sendEmptyMessage(1);
        mHandler.post(() -> Log.d(TAG,Thread.currentThread().getName()));
    }
}
复制代码

自启动到将 App 完全杀死,输出结果也是如此:loop 后面的代码没有执行!

2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 开始 会执行吗?  
2964-2964/com.example.dsd.demo D/HandlerDemo: start send message
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1  1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1
复制代码

这意味两个严重的问题:looper() 后面的代码一直都不会执行并且线程 Thread#1 将会一直运行下去!在 JVM 规范里面规定处于运行中的线程是会不被 GC 的。在没有消息的时候 Looper 会处于等待状态。等待在 Thread 的生命周期里仍然属于运行状态,它永远不会被 GC

因此不少网上不少文章里都有一个致命的缺陷,根本就没有说起到要在使用完毕后即便退出 Looper。紧接上文的代码

// 尝试 1秒 后中止
        try {
            Thread.sleep(1000);
            mLooper.quit();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
复制代码

此时的结果

2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 开始 会执行吗?  
2964-2964/com.example.dsd.demo D/HandlerDemo: start send message
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1  1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 结束 会执行吗?  
复制代码

根据综上所述,Handler 机制彻底能够在 Android 中用做线程间的消息同步,这里要强调一下,Handler 机制是 Android 独有的,笔者在写上面的 Demo 的时候居然傻傻的将 Handler 的启动放在了 Java 中,直接抛出了 RuntimException Stub 的错误。

总结一下在子线程中使用 Handler 机制要注意两点问题:

  1. 必须调用 Looper.prepare();手动生成为当前线程生成 Looper,并调用Looper.looper()启动内部的死循环。
  2. 必须在使用结束后调用 Looper.myLooper().quit()退出当前线程。

Handler

Handler 的工做主要就是发送和接收消息。消息的发送能够经过 post 的一系列方法和 send 的一系类方法。在建立 Handler 的时候他会默认使用当前线程的 Looper ,若是当前线程没有建立过 Looper 会抛出以下异常。

\[外链图片转存失败(img-i1B8Hwfw-1567932976368)(assets/image-20190908125220700.png)\]

固然也能够手动指定不一样线程的 Looper。

Handler mHandler = new Handler(Looper.getMainLooper());
复制代码

消息是如何发送到的呢?

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 final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, 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);
    }

复制代码

通过一系列的跟踪,最终的结果是调用了enqueueMessage(MessageQueue, Message, long)方法,目的就是为了向 MessageQueue 中插入一条消息。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

然后 nativeWake 将会唤醒等待的线程,MessageQueue#next将会在Looper.loop()中将这条消息返回,Looper.loop()在收到这条消息以后最终会交由 Handler#dispatchMessage处理

/** * Looper 的 loop 方法 */
public static void loop() {
  ......

    // 死循环
    for (;;) {
   ......
      // 开始分发消息 msg.target 指的就是发送消息的 Handler
       msg.target.dispatchMessage(msg);
   ......
    }
}
复制代码
/** * Handle 的 dispatchMessage 方法 */
    public void dispatchMessage(Message msg) {
       // 首先检查 msg 的 callback 是否为 null
        if (msg.callback != null) {
          // 不为 null 使用 msg 的 callback 处理消息
            handleCallback(msg);
        } else {
            // mCallback 是否为 null
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
          // 都没有指定则交由开发者重写的 handleMessage 处理
            handleMessage(msg);
        }
    }
复制代码

mCallback指的是一个接口 , 可使用 Handler handler = new Handler(Callback)的方式指定回调,这种方式能够由外部传递进来会回调方法,更加灵活。

* Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */ public interface Callback {
    /** * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */
    public boolean handleMessage(Message msg);
}
复制代码

至此对于 Android 的消息机制已经讲解完毕,你是否已经有了清晰的认识呢?对于开篇的问题:Handler 是如何完成线程切换的,你找到答案了吗?

常见问题分析

为何不能在子线程中更新 UI ,根本缘由是什么?

\[外链图片转存失败(img-h3PwlVnv-1567932976369)(assets/16c0641ad370a90a.jpeg)\]

mThread 是主线程,这里会检查当前线程是不是主线程,那么为何没有在 onCreate 里面没有进行这个检查呢?这个问题缘由出如今 Activity 的生命周期中 , 在 onCreate 方法中, UI 处于建立过程,对用户来讲界面还不可见,直到 onStart 方法后界面可见了,再到 onResume 方法后页面能够交互,从某种程度来说, 在 onCreate 方法中不能算是更新 UI,只能说是配置 UI,或者是设置 UI 属性。 这个时候不会调用到 ViewRootImpl.checkThread () , 由于 ViewRootImpl 没有建立。 而在 onResume 方法后, ViewRootImpl 才被建立。 这个时候去交户界面才算是更新 UI。

setContentView 知识创建了 View 树,并无进行渲染工做 (其实真正的渲染工做实在 onResume 以后)。也正是创建了 View 树,所以咱们能够经过 findViewById() 来获取到 View 对象,可是因为并无进行渲染视图的工做,也就是没有执行 ViewRootImpl.performTransversal。一样 View 中也不会执行 onMeasure (), 若是在 onResume() 方法里直接获取 View.getHeight() / View.getWidth () 获得的结果老是 0。

为何 Handler 构造方法里面的 Looper 不是直接 new ?

若是在 Handler 构造方法里面直接 new Looper(), 多是没法保证 Looper 惟一,只有用 Looper.prepare() 才能保证惟一性,具体能够看 prepare 方法。

MessageQueue 为何要放在 Looper 私有构造方法初始化?

由于一个线程只绑定一个 Looper ,因此在 Looper 构造方法里面初始化就能够保证 mQueue 也是惟一的 Thread 对应一个 Looper 对应一个 mQueue。

总结

  1. Android 的消息机制指的就是 Handler 消息机制,Handler 是面向开发者的上层接口,而底层的实现是 MessageQueue、Looper、ThreadLocal
  2. MessageQueue 使用单链表的数据结构承载消息,在 next 方法中 nativePollOne 方法会在消息为空的时候讲线程置为等待状态,直到有新的消息到来才会再次唤醒线程。因此 Looper.loop 虽然是死循环也不会卡死。
  3. Looper 的主要任务是不断尝试从 MessageQueue 中获取消息,为了避免让线程线性执行完毕,loop 中开启了一个死循环。由于主线程的全部生命周期都是由 Handler 机制完成的,因此这个主线程中死循环不容许开发者手动退出,何时 App 退出了,这个死循环才会被退出。而子线程中没有这机制,因此在使用完毕后必须手动退出,否者线程会一直处于等待状态,不会被GC。
  4. Handler 是借助 ThreadLocal 机制完成线程切换的,Handler 在建立的时候就已经获取了和线程绑定的 Looper,因此不管 Handler 在什么线程调用,最终都会回到 Looper 绑定的线程,因此 Handler 很适合在 Android 中作线程间通讯。

参考

  1. 《Android 开发艺术探究》第十章
  2. 知乎问答:《Android中为何主线程不会由于Looper.loop()里的死循环卡死》
  3. StackOverflow: 《android - what is message queue native poll once in android?》
  4. 《Handler 是如何作到发送延时消息的》
相关文章
相关标签/搜索