Skip to content

Handler

更新: 4/2/2026 字数: 0 字 时长: 0 分钟

Handler它在整个安卓体系中也扮演着重要的角色,所以也是面试过程中经常被问到的知识点,本节课就会对Handler进行一个较为详细的学习和分析。

Handler介绍

Handler的作用有两个:

  1. 在线程之间互发消息,实现线程间的通信
  2. 发送延时消息,延时执行任务

Handler线程通信

在Android中,UI的更新必须在主线程当中完成,但是如果我们在主线程进行网络请去的话,这会是一个很耗时间的过程,会导致主线程堵塞,整个应用的UI将无法响应用戶的操作,从而影响用戶体验,严重甚至会导致ANR(未响应)。

ANR(Application Not Responding,应用无响应),指Android系统发现你的应用在特定时间内没有响应用户操作时,弹出的错误。是系统通过与之交互的组件以及用户交互进行超时监控,用来判断应用进程是否存在卡死或响应过慢的问题。

ANR有四种类型,我们通常关注的是InputDispatchTimeout,代表的是对输入事件(如按键或触摸事件)5s内没响应。

为了避免这种问题,因此我们要用handler,将子线程获得的消息发送给主线程处理

这里使用一个模拟下载的demo介绍:

kotlin
class MainActivity : AppCompatActivity() {

  private lateinit var mTvProgress: TextView
  private lateinit var mPbProgress: ProgressBar
  private lateinit var mBtnDownload: Button

  private val mHandler = Handler(Looper.getMainLooper()) { msg ->
    when (msg.what) {
      1 -> {
        val progress = msg.data.getInt("progress")
        mPbProgress.progress = progress
        if (progress == 100) {
          Toast.makeText(this, "下载完成", Toast.LENGTH_SHORT).show()
          mBtnDownload.isEnabled = true
        }
        true
      }
      else -> false
    }
  }

  @SuppressLint("MissingInflatedId")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    mPbProgress = findViewById(R.id.progressBar)
    mBtnDownload = findViewById(R.id.btn_download)
    mTvProgress = findViewById(R.id.tv_progress)

    mBtnDownload.setOnClickListener {
      startDownload()
    }
  }

  private fun startDownload() {
    mBtnDownload.isEnabled = false
    mPbProgress.progress = 0

    Thread {
      for (progress in 1..100) {
        try {
          sleep(100)
        } catch (e: InterruptedException) {
          e.printStackTrace()
        }
        val msg = Message()
        msg.what = 1
        val bundle = Bundle().apply {
          putInt("progress", progress)
        }
        msg.data = bundle
        mHandler.sendMessage(msg)
      }
    }.start()
  }
}

Handler延时任务

最常用的就是banner的轮播以及倒计时了,这些用的都是handler的延时任务,在过一会后执行任务,这里以轮播图为例:

kotlin
runnable = object : Runnable {
    override fun run() {
        mVpHomeBanner.currentItem = mVpHomeBanner.currentItem + 1
        // 本质就是不断发送消息,更新UI
        handler.postDelayed(this, 3000)
    }
}

内存泄漏

什么是内存泄漏

内存泄漏,就是一个本该被回收的对象却无法被GC(垃圾回收)回收,造成了系统内存的浪费,最终可能导致应用OOM或者界面卡顿。

我们判断一个对象是否该被GC回收是通过判断这个对象是否“可达”。而内存泄漏的本质就是长生命周期持有短生命周期的引用,导致短生命周期无法被GC回收,最典型和最常用的短生命周期对象是ActivityFragment,它们泄漏会直接导致内存无法释放,引起性能问题。

比如静态变量持有了activity,静态变量生命周期很长,一直存在内存中。导致JVM认为activity还有用,就不会回收,从而造成内存泄漏。

内存泄漏的场景有很多比如单例类、非静态内部类的静态实例等等。

愤怒的小鸟(LeakCanary)

一个内存泄漏检测工具,在build.gradle.kts中导入依赖:

kotlin
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")

然后我们运行项目,就会发现多了一个小黄鸟APP:

img

然后我们在APP中到处点,如果出现了内存泄漏,那么APP就会给手机发通知,然后开始下载文件并进行分析,分析完成后又给你一个通知:

img

然后我们点进小黄鸟APP,就能看到为我们生成的发生内存泄漏的引用树:

image

这里我们观察到是因为MyThread类持有了Activity的引用导致了内存泄漏,然后我们查看代码:

kotlin
class MainActivity2 : AppCompatActivity() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        MyThread().start()
        findViewById<Button>(R.id.btn_click2).setOnClickListener {
//            Thread.sleep(30000)
            finish()
        }
    }

    inner class MyThread : Thread() {
        override fun run() {
            sleep(60*1000)
        }
    }

}

发现确实是这个原因。

解决内存泄漏

这里我们只关注一下Handler的内存泄漏问题:

  1. Handler作为内部类的时候,因为可以调用外部类的方法,所以默认会持有外部类的引用。导致假如Handler做一个耗时操作比如网络请求的时候退出activity,activity本来需要被销毁,但是Handler还在(后面会介绍为什么activity消失但是Handler还在),所以即便我们看不到activity,但是activity还在,被Handler持有着,导致内存泄漏。
  2. Handler作为内部类会持有外部类的引用很容易内存泄漏。

分别来讨论下如何解决:

  1. Handler生命周期没结束的原因是因为Handler中还有消息没有分发完毕,所以我们在activity被摧毁的时候将消息清空,这样就不会内存泄漏了。我们用到Handler的一个方法
kotlin
override fun onDestroy() {
    super.onDestroy()
    mHandler.removeCallbacksAndMessages(null)
}
  1. 假设我们现在activity有这么一个handler类:
kotlin
inner class MyHandler() : Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            1 -> {
                val progress = msg.data.getInt("progress")
                this@MainActivity.mPbProgress.progress = progress
            }
        }
    }
}

这里用的是非静态内部类,非静态内部类需要创建一个外部对象才能创建内部,因为非静态内部类需要访问外部类的成员,故一定需要一个实例,而这个实例的创建往往是隐式的。所以,我们可以发现,如果非静态内部类的静态实例会一直持有外部类的实例(隐式持有,因为他本身是一个非静态类),而静态实例会与类的生命周期绑定,即使外部类被销毁,静态实例也会持有外部的引用。那么如果这个实例是activity的话,就相当于长生命周期持有短生命周期的引用,则可能导致内存泄露。

要解决的话也是很简单的,我们使用静态内部类就可以不持有外部类的引用了,那我们如何访问这个activity呢?这里我们可以传参,但是如果传的又是一个activity,岂不是又会内存泄漏了,还不如直接用非静态内部类呢。所以,我们可以改用WeakRefrence即弱引用来引用activity,就不会内存泄漏了。

kotlin
class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {

    private val activityRef = WeakReference(activity)

    override fun handleMessage(msg: Message) {
        val activity = activityRef.get() ?: return
        when (msg.what) {
            1 -> {
                val progress = msg.data.getInt("progress")
                activity.mPbProgress.progress = progress
            }
        }
    }
}

然后我们再来看看四大引用:

强引用(Strong Reference):强引用在代码中普遍的存在,类似于“Object obj = new Object()”,只要某个对象有强引用与之关联,JVM则无法回收该对象,即使在内存不足的情况 下,JVM宁愿抛出OOM错误,也不会回收这种对象。

软引用(Soft Reference):软引用常常用来描述一些有用但是非必需的对象。对于软引用关联 的对象,会在JVM内存不足时既OOM之前将这些对象列入回收范围,进行二次回收。如果这时回收还是没有足够的内存才会造成内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软 引用。软引用一般用于网页的缓存图片的缓存等等比较耗时的操作,但是这些操作目前一般使用 LruChche来实现,因此目前代码中很少见到SoftReference。

弱引用(Weak Reference):被弱引用关联的对象只能生产到下一次垃圾收集发生之前。当垃圾 收集器工作室,无论内存是否足够,都会回收弱引用关联的对象。可以使用WeakReference类来 实现弱引用。

虚引用(Phantom Reference):虚引用也称之为幽灵引用,或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。也无法通过虚引用来 取得一个对象的实例,为一个对象设置为虚引用的唯一目的是希望这个对象被回收器回收时能收 到一个系统通知,在JDK1.2之后,提供了Phantom Reference类来实现虚引用。

Handler源码分析

然后我们来看一下Handler是如何做到线程之间的通信的。Handler主要运用的是消息队列的机制,那么什么又是消息队列机制呢?我们又会想到生产者-消费者模式,有三个角色:消息队列,生产者,消费者。而生产者和消费者都要从同一个消息队列里面存放消息,所以消息队列的数量是唯一的,生产者和消费者的数量可以任意。

但我们回到Android系统里,我们直到所有UI操作只能发生在同一个线程中,所以消费者线程只有一个,且消费者线程就是UI线程。而Handler就是这么一套提供在任意线程都可以发消息给UI线程的线程间通信工具

image.png

上面的这张图大概就是整个Handler的运行机制,可以看到,Handler相当于消息的生产者,每个Handler都会持有MessageQueue的引用,我们调用sendMessage()发送消息时,就会把消息保存到共享消息队列中。

在Handler机制中,消费者是Looper,或者是Looper.loop()所在的线程(Looper本质上知识对消息队列操作的封装),Looper.loop()方法负责取出消息然后交给Handler去执行。

除了发消息外,Android还为Handler增加了一些其他功能,比如子线程提交同步任务、异步消息和同步屏障等等,这些等后面具体分析源码时再说。

整套Handler消息队列机制有下面几个成员:

  1. Message:消息对象,类似于链表的一个节点
  2. MessageQueue:消息队列,用于存放消息对象的数据结构,链表
  3. Looper:消息队列的处理者(无限事件循环,不断从消息队列拿出消息发给相应的Handler处理)
  4. Handler:负责消息的发送和处理

下面我们就来详细看一下具体实现

Handler初始化

我们先来看看Handler类,从他的构造方法开始看,我们点进源码可以发现Handler的构造函数一共有七个,实际上我们能使用的只有两个(两个废弃,三个隐藏):

java
// hide
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
               boolean shared) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    mIsShared = shared;
}

这里是现在官方更推荐的通过显式传入Looper对象进行初始化,而非隐式使用当前线程关联的Looper。Android11中Handler无参构造函数就已经被标记为废弃了。

java
// 无参构造函数最后会回到这里来,可以看到这里隐式的使用了关联的Looper
public Handler(@Nullable Callback callback, boolean async) {
    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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    mIsShared = false;
}

我们可以发现最终的目的都是为了完成mLooper,mQueue,mCallback,mAsynchronous的初始化,我们也可以发现MessageQueue是由Looper来完成初始化的,且Handler对于Looper和MessageQueue是一对一的关系

Looper的初始化

我们在上面看Handler的初始化时,会要求我们传入Looper对象,在默认的构造方法中,会调用Looper.myLooper()来获取和当前线程的Looper对象,再从中获取MessageQueue。如果获取Looper对象为null则会抛出异常Can't create handler inside thread that has not called Looper.prepare(),我们可以发现在初始化Handler之前需要调用Looper.prepare()方法完成初始化。

然后我们先来看看这里的myLooper()方法:

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

这是Looper类的一个静态方法,只是单纯的从sThreadLocal变量中取值并返回而已。然后我们又好奇这里的sThreadLocal又是什么呢?我们点进去看:

java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

哦这里是一个静态的ThreadLocal类型的变量,每个线程访问的都是这个变量。他为每个线程维护了一个Looper对象,调用get()方法即可取出来,我们来看看如何实现存放的:

java
public class ThreadLocal<T> {
    //others
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获得这个线程的Map
        ThreadLocalMap map = getMap(t);
        // 如果map为空就初始化,不为空就往里面放
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //others
}

我们可以看到每个线程都会从中读取一个自己的ThreadLocalMap来实现不同线程访问同一个静态ThreadLocal对象取到不同的值。

我们再来看看这里的map.set()方法,点进去看:

java
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    // 创建一个Entry数组,Entry是一个键值对
    Entry[] tab = table;
    int len = tab.length;
    // 计算hash值
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {

        // Android-changed: Use refersTo() (twice).
        // ThreadLocal<?> k = e.get();
        // if (k == key) { ... } if (k == null) { ... }
        
        // key相同直接覆盖值
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }
        
		// key为null,但是值不为null,就赋值并清空所有key为null值不为null的数据
        if (e.refersTo(null)) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // key为null,值为null,赋值并且清空所有key为null值不为null的数据
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
  1. 第一种情况,e也就是Entry(目前先理解为一个键值对),如果它的键和我们传进来的key(Threadlocal)相等,那么我们就将值替换掉
  2. 第二种情况,replaceStaleEntry方法内部会将新Entry的值全部赋值到旧Entry中,同时会将所有数组中key为null的Entry全部置为null
  3. 第三种情况,简单的遍历删除所有位置下key==null的数据

那么如果遇到了hash冲突怎么办,即计算hash值时遇到了hash值一样的。我们可以通过开放地址法解决,即通过向前或者向后寻找空位置来放置hash冲突的对象。

总之,ThreadLocal会跟随线程存在,并有较长的生命周期,对于不同的线程我们能取到不同的值,这里取到的就是Looper对象。

既然myLooper()方法是Looper类的静态方法,只是单纯从sThreadLocal变量中取值并返回,那么又是在哪里进行初始化的呢?上面提到过在初始化Handler之前需要调用Looper.prepare()方法完成初始化,那么我们来看看这个Looper.prepare()方法:

java
public static void prepare() {
    prepare(true);
}

// quitAllowed 是否允许被停止(清空消息队列) 只有主线程默认false,其它默认都为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));
}

我们再来看看这里的new Looper()方法发生了什么:

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

quitAllowed就是只有主线程的prepareMainLooper调用prepare默认传入false,其他都是true,因为主线程肯定是不允许停止的。

然后我们发现这里初始化了一个MessageQueue,这里我们后面再看,这个在消息插入和消费时经常出现。

我们还发现这里的构造函数是私有的,会初始化这两个常量值,说明Looper对于MessageQueue和Thread都是一一对应的关系,关联后不再改变

总结起来就是一个线程有一个ThreadLocalMap,Looper.prepare()new了一个Looper,以ThreadLocal作为键,Looper对象作为值存放在线程中的ThreadLocalMap中。

之前你使用Looper的无参构造时是可以进行UI刷新操作的,这里使用的是和主线程关联的Looper对象,在Looper类中是一个静态变量sMainLooper

java
@UnsupportedAppUsage
private static Looper sMainLooper;  // guarded by Looper.class

//被标记为废弃的原因是因为 sMainLooper 会交由 Android 系统自动来完成初始化,外部不应该主动来初始化
@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

prepareMainLooper()就用于为主线程初始化Looper对象,该方法又是在ActivityThread类的main()方法里面调用的,这个方法是java程序运行起点,在应用启动时系统就会为我们在主线程做好了mainLooper初始化,而且已经调用了Looper.loop()方法开启了消息的循环处理,应用在使用过程中的各种交互逻辑(例如:屏幕的触摸事件、列表的滑动等)就都是在这个循环里完成分发的。

java
public final class ActivityThread extends ClientTransactionHandler {
 
    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
        ···
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
}

Handler发送消息

先来看看Handler如何发送消息,我们点进sendMessage()的源码进去看看:

java
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

继续跟进

java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

前面是判断传入的delayMillis时间的合法性,然后又调用了sendMessageAtTime()方法,这里第二个参数代表的是这条消息对应的时间戳,这里的SystemClock.uptimeMillis()指的是系统启动到现在的时间,硬件层,而不是当前系统时间,防止用户篡改系统时间导致消息插入异常。然后我们继续跟进方法:

java
public boolean sendMessageAtTime(@NonNull 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()方法,我们点进去看:

java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    // 这里的this是一个Handler
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    // 判断是不是为异步消息,如果Handler构造时设置为异步这里也将消息设置成异步
    // 同步消息,异步消息,同步屏障在后面会讲
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 调用MessageQueue的enqueueMessage方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

我们先来看看Message消息类是什么东西:

java
public final class Message implements Parcelable {
    // 唯一标识,区分我们要处理的任务
    public int what;

    // 传递简单的整数值
    public int arg1;
    public int arg2;

    // 用于存储任意类型的对象,适用于传递单个复杂对象或自定义数据
    public Object obj;

    // 时间戳
    public long when;

    // 存储键值对形式的数据,适用于传递多个不同类型的数据或复杂的数据结构
    Bundle data;
    
    // 指向Message的发送者,也是最终处理者
	Handler target;

    // Runnable任务
    Runnable callback;

    // 下一个节点
    Message next;
}

上面的代码就是先对Message进行一些属性的赋值,然后调用了queue的enqueueMessage()方法,我们来详细看看。

消息的插入

MessageQueue通过enqueueMessage方法来接收消息,MessageQueue内部是用链表来存储Message的,根据时间戳大小决定在消息队列里的位置。mMessages 代表的是消息队列中的第一条消息。如果 mMessages 为空(消息队列是空的),或者 mMessages 的时间戳要比新消息的时间戳大,则将新消息插入到消息队列的头部;如果 mMessages 不为空,则寻找消息列队中第一条触发时间比新消息晚的非空消息,将新消息插到该消息的前面。

说完上面这些,我们紧接着来看看源码:

java
public final class MessageQueue {
    //others
    boolean enqueueMessage(Message msg, long when) {
        // 如果target为空,就报错
        // 但是刚才我们设置消息的target为this,Handler一定是不为null的,这里的为null是给谁看的?
        // 有可能不是刚才那个方法调用这个方法,可能是别的方法调用啊
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        
        synchronized (this) {
            // 处于使用状态的Message别来沾边
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            // 如果都暂停了,那就别入队了,洗洗睡吧
            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();  // 标记当前Message为使用状态
            msg.when = when;  // 设置Message的when属性,也就是要执行的时间
            Message p = mMessages;  // 保存队列头
            boolean needWake;
            //1. 如果当前队列本来没头,那新来了一个人,那个人就是头
            //2. 如果when==0也就是现在发生,直接放到队列头
            //3. 如果比队列最早的还要早发生,那也可以当头
            if (p == null || when == 0 || when < p.when) {
                //让当前msg当头的代码
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 1.如果消息队列被锁住(也就是休眠)
                // 2.当前队列头头的Handler为null(同步屏障)
                // 3.要插入的消息是异步消息
                // 这三个条件都满足,那么我们才需要唤醒
                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;
                    }
                }
                // 将Message插入消息队列
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
    
            // 唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    //others
}

上面提到了异步消息,同步屏障,还有同步消息,我们接下来来看看。

同步消息&异步消息&同步屏障

其实很简单,同步消息和异步消息就是target不为空的Message,同步屏障就是target为null的Message。同步消息正常读取,异步消息在没有遇到同步屏障的时候和同步消息同等地位,遇到同步屏障的时候,会跳过同步屏障后面所有同步消息,直接找到第一个异步消息。所以起名叫做同步屏障了。这种机制会使得在异步消息被执行完之前同步消息都不会得到处理。

Handler中的构造函数的async参数就用于控制发送的Message是否为异步消息:

java
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
               boolean shared) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async; // 这个参数
    mIsShared = shared;
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        // 设置为异步消息
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue在获取队头消息时,如果判断到队头消息的target为null,就知道该Message属于屏障消息,那么就会再向后遍历找到第一条异步消息优先进行处理。我们可以通过调用Message的setAsynchronous(true)方法主动发送异步消息,但是如果要发送屏障消息的话,需要通过反射调用MessageQueue的postSyncBarrier()方法(因为这个方法被隐藏了),我们来看看这个方法:

java
public final class MessageQueue {
    //others
    public int postSyncBarrier() {
        // 调用方法,传入进入app以来的时间
        return postSyncBarrier(SystemClock.uptimeMillis());
    }
   
    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            // 获得Message,设置基本属性,但是不设置target,即target为null
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
    
            Message prev = null;
            Message p = mMessages;
            // 找到位置插入这个消息
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            //如果是头,就放到头上,不是头就插入链表中间
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
    //others
}

大体上的插入逻辑是类似的,就是把target设置为了null。

Looper.loop()

在之前ActivityThread类里面,发现最后会调用一个Looper.loop(),加上这行代码后,我们的Handler才能实现回调,我们来看下这个方法:

java
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
               + " before this one completed.");
    }

    me.mInLoop = true;

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

    // Allow overriding a threshold with a system prop. e.g.
    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    final int thresholdOverride = getThresholdOverride();

    me.mSlowDeliveryDetected = false;

    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

这里主要的地方有两个,一是myLooper(),二是loopOnce()

我们先来看看myLooper方法,之前提到过,返回的就是sThreadLocal.get(),是一个与当前线程相关联的Looper,我们点进这个get方法看看:

java
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

也是取出当前线程的ThreadLocalMap,然后取出Entry对应的ThreadLocal键的值并返回,如果为空就会返回setInitialValue(),这里就是如果map为空就新建一个ThreadLocalMap,如果不为空就把当前值设为value,不过这里value是null。

java
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}
protected T initialValue() {
    return null;
}

然后我们再来看看loopOnce():

java
private static boolean loopOnce(final Looper me,
                                final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ...
    try {
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    ...
    msg.recycleUnchecked();

    return true;
}

我们依次来看,先来看这个Message msg = me.mQueue.next(),这里是从MessageQueue中取出了Message,也就是我们的消息。然后我们是调用的looper里面的mQueue也就是消息队列的next方法,我们来看看这个方法:

java
public final class MessageQueue {
    
    Message next() {
        // 当已经调用dispose方法或者quit方法时返回null。
        // 这种情况可能存在,在停止之后重启looper的时候。
        // 调用quit方法,mQuitting为true -> 下面进入循环调用dispose() -> ptr = 0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        
        int pendingIdleHandlerCount = -1; // 只在第一次迭代期间为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            //others
            // 唤醒方法,在nextPollTimeoutMillis时间后唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                // 尝试检索下一条消息。如果找到,则返回。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //同步屏障 会跳过同步消息只看异步消息
                if (msg != null && msg.target == null) {
                    // 被屏障阻塞。在队列中找到下一条异步消息。
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 下一条消息尚未准备好。设置一个超时以在准备好时唤醒。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 收到一条消息
                        mBlocked = false;
                        // 取出当前Message,让Message的前后节点拼接
                        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 {
                    // 没有更多的消息。-1代表一直睡眠
                    nextPollTimeoutMillis = -1;
                }
    
                // 处理退出消息,现在所有挂起的消息都已处理完毕。
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //下面是处理IdleHandler,这是在没有任何消息的情况下才会走到这里,说明 idleHandler的消息级别最低
                // 如果是第一次空闲,则获取要运行的空闲处理程序数。
                // 仅当队列为空或队列中的第一条消息(可能是一个屏障)的时间在将来时,才运行空闲处理程序。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 没有要运行的空闲处理程序。继续循环等待。
                    mBlocked = true;
                    continue;
                }
    
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
    
            // 运行空闲处理程序。
            // 我们仅在第一次迭代期间到达此代码块。
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // 释放对处理程序的引用
    
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
    
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
    
            // 将空闲处理程序计数重置为0,以便我们不再运行它们。
            pendingIdleHandlerCount = 0;
    
            // 在调用空闲处理程序时,可能会传递一条新消息
            // 因此返回并再次查找一个挂起的消息而不等待。
            nextPollTimeoutMillis = 0;
        }
    }
    //others
}

读取消息的操作对应的是next()方法,方法内部开启了一个无限循环,去获取到时间的Message。如果遇到同步屏障(target==null),则跳过所有同步消息直到找到异步消息为止。然后轮询消息,如果下一条消息还没到,那么就调用nativePollOnce(ptr, nextPollTimeoutMillis)方法进行休眠,nextPollTimeoutMillis 值为要等待的时间。

然后我们再来看看IdleHandler:

java
public static interface IdleHandler {
    boolean queueIdle();
}

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

IdleHandler 是 MessageQueue 的一个内部接口,可以用于在 Loop 线程处于空闲状态的时候执行一些优先级不高的操作,通过 MessageQueue 的 addIdleHandler 方法来提交要执行的操作。比如ActivityThread就向主线程添加了一个GcIdler,用于在主线程空闲时执行GC操作:

java
public final class ActivityThread extends ClientTransactionHandler {
    
    @UnsupportedAppUsage
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //添加 IdleHandler
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
    
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            //尝试 GC
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }
    
}

然后我们再回去看分发事件的核心代码msg.target.dispatchMessage(msg),这里的target是一个Handler:

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

逻辑很简单,就是三层if else进行判空处理,不为空就调用,先后顺序分别为Message的callback,Handler传入的callback,然后就是Handler的handleMessage方法。我们之前写的网络请求就是在handleMessage方法中回调处理。

那么loop方法里的无限循环会导致卡死吗,其实是不会的:

  • 在安卓中,我们可能随时点击屏幕,我们需要一个能够持续不断监听事件或者说处理事件的东西,而主线程的loop死循环就完美的解决了这个问题。
  • 而且没有消息的时候,handler会阻塞,主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。
  • 真正导致ANR的原因不是死循环,而是因为在某个消息处理的时候操作时间过长

Looper.quit()

java
public void quit() {
    mQueue.quit(false);
}

然后我们点进去看:

java
void quit(boolean safe) {
    // 是否允许暂停,主线程默认为false
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    if (mUseConcurrent) {
        synchronized (mIdleHandlersLock) {
            if (sQuitting.compareAndSet(this, false, true)) {
                if (safe) {
                    removeAllFutureMessages();
                } else {
                    removeAllMessages();
                }

                // We can assume mPtr != 0 because sQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    } else {
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                // 移除所有未来的消息,但在使用的Message不会被移除
                removeAllFutureMessagesLocked();
            } else {
                // 移除所有的消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            // 唤醒
            nativeWake(mPtr);
        }
    }
}

再来看看这两个移除消息的方法:

java
public final class MessageQueue {
    //others
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                // 如果所有的事件都还没发生,那直接移除
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    // 如果找不到未来的消息,那么也不用移除了
                    if (n == null) {
                        return;
                    }
                    // 找到过去与未来的交界处,现在。
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                // 往后遍历,挨个移除消息
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
    //others
}

这个代码也很好理解。

总结

  1. 每个 Handler 都会和一个 Looper 实例关联在一起,可以在初始化 Handler 时通过构造函数主动传入实例,否则就会默认使用和当前线程关联的 Looper 对象
  2. 每个 Looper 都会和一个 MessageQueue 实例关联在一起,每个线程都需要通过调用 Looper.prepare()方法来初始化本线程独有的 Looper 实例,并通过调用Looper.loop()方法来使得本线程循环向 MessageQueue 取出消息并执行。Android 系统默认会为每个应用初始化和主线程关联的 Looper 对象,并且默认就开启了 loop 循环来处理主线程消息
  3. MessageQueue 按照链接结构来保存 Message,执行时间早(即时间戳小)的 Message 会排在链表的头部,Looper 会循环从链表中取出 Message 并回调给 Handler,取值的过程可能会包含阻塞操作
  4. Message、Handler、Looper、MessageQueue 这四者就构成了一个生产者和消费者模式。Message 相当于产品,MessageQueue 相当于传输管道,Handler 相当于生产者,Looper 相当于消费者
  5. Handler 对于 Looper、Handler 对于 MessageQueue、Looper 对于 MessageQueue、Looper 对于 Thread ,这几个之间都是一一对应的关系,在关联后无法更改,但 Looper 对于 Handler、MessageQueue 对于 Handler 可以是一对多的关系
  6. Handler 能用于更新 UI 包含了一个隐性的前提条件:Handler 与主线程 Looper 关联在了一起。在主线程中初始化的 Handler 会默认与主线程 Looper 关联在一起,所以其 handleMessage(Message msg) 方法就会由主线程来调用。在子线程初始化的 Handler 如果也想执行 UI 更新操作的话,则需要主动获取 mainLooper 来初始化 Handler
  7. 对于我们自己在子线程中创建的 Looper,当不再需要的时候我们应该主动退出循环,否则子线程将一直无法得到释放。对于主线程 Loop 我们则不应该去主动退出,否则将导致应用崩溃
  8. 我们可以通过向 MessageQueue 添加 IdleHandler 的方式,来实现在 Loop 线程处于空闲状态的时候执行一些优先级不高的任务。例如,假设我们有个需求是希望当主线程完成界面绘制等事件后再执行一些 UI 操作,那么就可以通过 IdleHandler 来实现,这可以避免拖慢用户看到首屏页面的速度

上面源码分析可能有点绕,但是多看几遍总能看懂的,那么现在我们就知道为什么子线程的消息主线程能收到了,因为子线程创建的Message插入的是主线程的消息队列。详细分析一下就是主线程中创建了Looper,然后Looper在初始化的时候就会初始化MessageQueue,所以MessageQueue是主线程的消息队列。然后子线程插入消息的时候根据时间插入,然后主线程loop死循环取出事件并且消费。