Android - 再探Handler

Android - 再探Handler

  • Handler内存泄漏根源与规避
  • 主线程Handler与子线程Handler的差异
  • Looper:消息队列空闲处理,死循环为什么不会卡死App
  • MessageQueue:多个Handler并发入队的线程安全、消息屏障
  • Looper底层epoll唤醒机制
  • ThreadLocal:Looper在线程隔离的原理
  • Message:复用池与享元模式,正确的创建方式
  • 子线程创建Handler标准写法
  • HandlerThread原理:如何安全拿到子线程Looper

Android开发中,Handler可以说是我们最早接触的异步消息组件。子线程切主线程、UI的更新,几乎每个项目都能见到它的身影。

很多同学对Handler的认知,止步于面试背诵,知道Looper循环、知道匿名内部类会内存泄漏。但是一旦遇到消息屏障、队列空闲处理、epoll阻塞唤醒这类底层问题,就容易理解断层。

本文不会简单罗列API,而是顺着消息发送到完整链路,从Handler、Message、MessageQueue、Looper逐层拆解,从源码入手,读完你不仅能应对面试,遇到相关疑难问题也能从源码角度定位。

本章内容基于对Handler有基础了解的同学,基础薄弱的同学可以先阅读上一章内容,让内容更具连贯性

Handler内存泄漏的原因?怎么规避

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    // 匿名内部类Handler
    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            // UI更新逻辑
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 延时消息,延迟10s执行
        handler.postDelayed({
            Log.d("test", "延时任务")
        }, 10000)
    }
}

观察上面这段代码,在打开页面后,立刻关闭Activity。此时的Activity本该被GC回收,但是实际情况却是发生了内存泄漏。 在上述代码中,包含了两处匿名内部类:Handler对象、postDelayed的Runnable,二者都会隐式持有Activity的引用。下面我们将从源码层面拆解这条引用链的形成原因。

java 复制代码
// Handler中的数据传递链
Handler.java 
    -> sendMessage()
        -> sendMessageDelayed()
            -> sendMessageAtTime
                -> enqueueMessage(queue, msg, uptimeMillis);
                    // 将当前Handler对象赋值到Message对象中,确保后续处理的Handler一致
                    // 赋值后,Handler对象就被Message对象持有
                    msg.target = this
                    // 调用MessageQueue对象的enqueueMessage方法用于处理msg对象
                    queue.enqueueMessage(msg, uptimeMillis);
MessageQueue.java
    -> enqueueMessage()
        // 将Handler中的Message放入到MessageQueue队列中
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
        // 看到这里,很多人会疑惑:谁持有 MessageQueue?答案是 Looper,下文会展开说明。
Looper.java
    -> loop()
        // loop方法为死循环方法,不断的从MessageQueue队列中取出Message进行处理
         for (;;) {
			// 取出MessageQueue中的Message
            Message msg = queue.next(); // might block
            ......    
            // 从MessageQueue中获取到Message后,通过dispatchMessage方法分发Msg
            msg.target.dispatchMessage(msg);
         }
    -> Looper()
        // Looper类的私有构造方法中创建MessageQueue对象并持有MessageQueue对象
ActivityThread.java
    -> main()
        -> Looper.prepareMainLooper();
            -> prepare(false);
                -> Looper.sThreadLocal.set(new Looper(quitAllowed))
                    // sThreadLocal作为一个静态的ThreadLocal对象持有Looper对象

// 整体的对象引用链
sThreadLocal -> Looper -> MessageQueue -> Message -> Handler -> Activity

总结一下,在上述数据传递过程中 整体对象引用链是这样的,在ActivityThread.main()方法中执行Looper.prepareMainLooper();初始化Looper,Looper会把自身存入内部的sThreadLocal静态ThreadLocal对象中,Looper初始化时,同步创建MessageQueue并持有它。回到ActivityThread.main()方法中执行Looper.loop();开始执行loop死循环,不断的取出MessageQueue中的Message对象进行处理, Message对象中的target字段持有Handler实例,Handler作为一个非静态内部类在Activity中使用,又间接持有了外部Activity实例的引用,形成了引用链,这就是造成内存泄漏的根源。

如何规避,Android中的GC采用的是可达性分析算法,引用链中的sThreadLocal作为GC ROOT,导致引用链中的Activity对象不可被回收,规避方法就是断开引用链,涉及到一个基本的概念:非静态内部类会隐式持有外部类实例对象,静态内部类对象不会持有外部类实例对象。我们这儿就可以改造Handler为静态内部类,同时使用弱引用包装Activity。

修复后的代码

kotlin 复制代码
class MainActivity : AppCompatActivity() {

    // 静态嵌套Handler,不持有外部Activity强引用
    private class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
        // 弱引用包装Activity
        private val weakRef = WeakReference(activity)
        override fun handleMessage(msg: Message) {
            val activity = ref.get()
            // 先判断Activity是否存活,不为空再操作UI
            activity?.let {
                // UI更新逻辑
            }
        }
    }

    private val handler = MyHandler(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handler.postDelayed({
            Log.d("test", "延时任务")
        }, 10000)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 页面销毁,移除所有消息,切断引用,根治泄漏
        handler.removeCallbacksAndMessages(null)
    }
}

主线程Handler与子线程Handler的差异

先说差异,再上源码。

主线程启动时,ActivityThread.main()中就会去准备Looper -> Looper.prepareMainLooper(); 所以可以直接在主线程中去使用Handler。

子线程中去实例化Handler对象时,我们需要在创建Handler之前自己去准备Looper -> Looper.prepare();并在其之后执行Looper.loop()方法让MessageQueue循环起来。子线程在Looper使用完毕后,必须手动调用looper.quit()或者looper.quitSafely()终止循环,释放资源,否则子线程会一直阻塞常驻,无法回收。

子线程中不准备Looper直接去new Handler()报错根源,如果不准备Looper,在初始化Handler时就会因为找不到mLooper而直接报错

Java 复制代码
Handler.java
    -> Handler()         
        ->  mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }

需要注意的是: 在主线程中调用的是Looper.prepareMainLooper();子线程中调用的是Looper.prepare();两者的区别在于前者quitAllowed = false;主线程的Looper不允许退出,调用quit()会抛出异常。

arduino 复制代码
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
Java 复制代码
// ActivityThread中对Looper的初始化
ActivityThread.java
    -> main()
        -> Looper.prepareMainLooper();
        -> Looper.loop();

子线程中使用Handler的正确姿势

kotlin 复制代码
Thread {
    // 第一步:准备Looper,创建MessageQueue并绑定到当前线程
    Looper.prepare()

    // 第二步:当前线程Looper就绪,才可以实例化Handler
    val handler = Handler(Looper.myLooper()!!) { msg ->
        Log.d("handler", "收到消息:${msg.what}")
        true
    }
    handler.sendEmptyMessage(1001)

    // 第三步:开启消息循环,阻塞等待消息
    Looper.loop()

    // 注意:looper退出后,代码才会走到这一行
    Log.d("handler", "looper 已退出,线程结束")
}.start()

Looper:消息队列空闲时,死循环为什么不会卡死App?

很多同学存在一个误区: Looper.loop()是for(;;)无限循环,不断轮询取Message,会一直占用CPU,造成应用卡死。这个理解是错误的。 先看源码

Java 复制代码
Looper.java
    -> loop()   // 核心代码片段
    for (;;) {
        Message msg = queue.next(); // might block
    }
MessageQueue.java
    -> next()
        -> nativePollOnce(ptr, nextPollTimeoutMillis);  // 睡眠
    -> enqueueMessage()
        ->  if (needWake) {
                nativeWake(mPtr);   // 唤醒
            }

关键在于queue.next()方法,源码中的注释为 -- might block(可能阻塞)深入该方法后可以看到next()方法中的nativePollOnce()方法,在MessageQueue里没有待处理消息时,会调用nativePollOnce()方法进入阻塞休眠。线程释放CPU,不再占用运算资源。

底层依靠epoll机制监听文件描述符,在有新消息入队时,会写fd唤醒阻塞的线程,继续取出消息执行。所谓的"死循环"并不是忙轮询,是在无消息时进入线程休眠,释放CPU,在有消息时唤醒线程进行消息处理

补充区分:ANR的本质不是Looper死循环。ANR是主线程在处理消息的时候,消息内部执行了耗时操作,导致后续消息长时间得不到处理而造成的,不是loop循环本身造成卡死。loop只是不断取出消息进行执行,本身不会阻塞线程。只有单个Message里面代码耗时,才会ANR。

Looper的循环是阻塞式等待,非忙循环,空闲时线程休眠,不会持续占用CPU,所以不会卡死App。

MessageQueue:多个Handler并发入队的线程安全、消息屏障

MessageQueue底层是单向链表,消息执行时间按照执行时间when从小到大排序,只能从链表头部依次取出。那系统如何实现在不打乱时间排序的前提下,优先处理某些优先级较高的消息呢?

MessageQueue本质是单向链表,并不是并发队列。多个不同的Handler可以同时调用enqueueMessage往队列插入消息,那它如何保证并发安全?

先看源码

Java 复制代码
MessageQueue.java -> enqueueMessage()
 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            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();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
			// 往MessageQueue  消息队列里面添加Handler中的消息
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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自身对象锁中。不管多少个Handler、不管在哪个线程调用enqueueMessage,同一时刻只能有一个线程能操作链表。next()取消息方法中,同样将消息取出的操作都包裹在MessageQueue自身对象锁中。

消息屏障(同步屏障) 消息屏障本质是target==null的特殊Message,不会被执行,仅起到标记作用。 依旧源码对比

Java 复制代码
// 正常的Message
Handler.java -> enqueueMessage
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;  // target赋值为当前Handler对象
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
// 消息屏障的特殊Message
// 没有进行target的赋值
MessageQueue.java -> postSyncBarrier()
    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            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;
        }
    }

在消息取出时,MessageQueue->next(),屏障消息的target为null,通过while循环优先将异步消息取出,优先进行消息发送

Java 复制代码
	// 屏障消息的target是为null的,当发现target为null时  就确定是屏障消息
    if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        // 通过while循环优先将异步消息取出,优先进行消息的发送
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }

遇到屏障消息,只处理异步消息(isAsynchronous=true),跳过普通同步消息,在屏障使用完成后,必须手动移除屏障removeSyncBarrier(),否则队列永远不会恢复处理同步消息。

注意:在我们日常用到的普通Handler发送的消息默认都是同步消息,只有设置了msg.isAsynchronous = true的才是异步消息。在Android的底层,用消息屏障做UI渲染,View绘制流程Choreographer依靠消息屏障,优先处理VSYNC信号的异步消息,保证画面刷新的实时性。

补充:postSyncBarrier()和removeSyncBarrier()属于 Android 系统 hide API,应用层无法直接调用。消息屏障是框架内部能力,很典型的场景就是Choreographer的VSYNC信号,保证屏幕刷新新信号优先执行,确保画面的实时刷新。 同时,插入消息屏障时不会唤醒消息队列,屏障的目的就是暂停同步消息的执行。

Looper底层epoll唤醒机制

在前面的章节提到,当MessageQueue没有消息时,MessageQueue.next()会阻塞。这个阻塞能力,就是依赖底层的Linux中的epoll IO多路复用机制。 Handler里的完整epoll工作流程:

  1. MessageQueue初始化native层时,创建Linux管道pipe:有读fd、写fd
  2. nativePollOnce:调用epoll_wait,阻塞监听管道读fd。 a. 队列无消息:epoll_wait阻塞,线程休眠,释放CPU b. 来了新消息:调用nativeWake,往管道写fd写入一个字节数据
  3. epoll检测到管道读fd有数据就绪,epoll_wait返回,唤醒JAVA层线程,继续执行next()取出消息
  4. 读完管道里的数据后,清空管道,回到循环继续等待。

注意:epoll是通用IO多路复用,Handler只是拿它做线程休眠/唤醒

epoll是Linux内核提供的IO多路复用技术:可以用单个线程监听多个文件描述符(fd)。当监听的fd没有事件到来时,调用epoll_wait会让当前线程进入休眠,释放CPU;一旦fd有事件就绪,内核唤醒线程继续执行循环。

C++ 复制代码
// 通过epoll_wait()方法阻塞线程
android_os_MessageQueue.cpp
    -> android_os_MessageQueue_nativePollOnce()
        -> pollOnce()
            -> Looper.cpp->int Looper::pollOnce()
            -> Looper.cpp->int Looper::pollInner()
                ->
                // 这就是利用 epoll_wait() 进行阻塞等待,这里的 timeoutMillis是阻塞等待时间,来自Java层的MessageQueue
                int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 通过往管道中写入fd写入一个数据唤醒线程,继续执行Looper循环
android_os_MessageQueue.cpp
    -> android_os_MessageQueue_nativeWake()
        -> wake()
            -> Looper.cpp->void Looper::wake()
                -> 
                // 往mWakeEventFd中写入字符1,用以唤醒线程
                ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));

ThreadLocal:Looper在线程隔离的原理

先回顾ThreadLocal在Handler里的使用场景

Java 复制代码
    // 将Looper set到ThreadLocal中
    Looper.java
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 将Looper set到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

    // 将Looper从ThreadLocal中get出    
    Looper.java
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

前面我们知道,Looper是绑定到线程的。Looper.myLooper()为什么在不同线程调用,拿到的Looper不一样?怎么做到的线程隔离? 每个Thread对象内部持有ThreadLocalMap,map存储Entry,key为ThreadLocal对象,value存放Looper,不同线程的ThreadLocalMap相互独立,A线程set的数据,B线程读取不到。观察ThreadLocal中的get()方法和set()方法

Java 复制代码
    // get()
    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();
    }

    // set()
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

get()和set()方法中的第一行都是Thread t = Thread.currentThread();拿到当前线程自带的ThreadLocalMap;再以ThreadLocal实例作为key,在该map存取Looper。每个线程的ThreadLocalMap互相独立,A线程存入的数据,B线程读取不到,以此实现线程隔离。

回顾之前章节中提到的主线程创建Handler和子线程创建Handler的区别,调用prepare()时,通过sThreadLocal.set(),将Looper存入当前线程的ThreadLocalMap集合中。调用myLooper(),通过sThreadLocal.get(),读取当前线程绑定到Looper。

如果未执行prepare()方法,在new Handler时会调用Looper.myLooper()方法,获取到的looper==null时则会弹出异常。

这也解释了:子线程没有调用Looper.prepare(),myLooper()拿到null,直接new Handler就抛出异常

Message:复用池与享元设计模式,正确的创建方式

享元设计模式核心:复用已有对象,共享实例,减少内存分配。Message池就是缓存闲置Message对象,重复利用。、

日常写Handler消息时,很多同学习惯性直接new Message()创建实例。但Android源码推荐使用Message.obtain(),背后就是享元设计模式的思想,减少了频繁创建销毁对象带来的内存开销与GC压力。

在Message内部维护了一个静态消息缓存池sPool,本质是一个单向链表,最大缓存数量50个

Java 复制代码
    // Message.java
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;

    // 从池子中获取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();
    }

    // 清空Message对象内部值,标记Message为未使用状态重新放回池中
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;	// 将Handler对象置空,断开内存泄漏的引用链
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

在创建Message对象时,优先从缓存池中取出闲置的Message;缓存中为空时,才new Message()。

消息处理完后,msg.recycle()会调用recycleUnchecked():清空Message全部字段值内容,并且将Message对象标记为未使用状态;如果当前缓存池中的对象未满(50个),则将该Message放回sPool链表中。

在池子满了之后,不再回收Message存入池子中,对象交给GC进行回收。sPoolSync对象锁:多线程并发存取消息池,加锁保证线程安全。

创建Message的正确写法

Kotlin 复制代码
    // 方式1:调用静态obtain方法
    val msg = Message.obtain()
    msg.what = 1001
    handler.sendMessage(msg)

    // 方式2:handler自带obtainMessage
    // 其内部调用的还是obtain方法
    val msg2 = handler.obtainMessage(1001)
    // 下列代码等同于 handler.sendMessage(msg2)
    msg2.sendToTarget()

Message中的复用缓存池只是缓存优化,不是无限缓存,上限50个;超出50个对象,新new的Message对象会直接交给GC进行处理。

子线程创建Handler标准写法

回顾前面章节,在主线程中App启动时系统自动调用Looper.prepareMainLooper()和Looper.loop();所以在主线程中可以直接 new Handler()

但子线程中默认是不存在Looper的,在new Handler()时会检查是否存在Looper,如果不存在则会抛出异常: "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");

想要在子线程中正常使用Handler,有严格的执行顺序: Looper.prepare() -> new Handler() -> Looper.loop()

Java 复制代码
public void method() {
    new Thread(new Runnable() {
		@Override
		public void run(){
            Looper.prepare();
			threadHandler = new Handler(new Handler.Callback() {
				@Override
				public boolean handleMessage(Message msg) {
					return false;
				}
			});
			Looper.loop();
			// Code
		}
	}).start();
}

上述代码中展示了在子线程中如何创建一个Handler对象,用于执行Handler发送的Message,但是存在一个弊端,只有通过threadHandler.sendMessage()才能将需要处理的消息发送到当前子线程进行处理。如果存在多个Handler发送的消息需要放到同一个子线程中进行统一的消息处理,该怎么做?

解决方案:定义一个继承Thread的类

Java 复制代码
    public class MyHandler extends Thread{
		Looper looper;
		MyHandler(String name) {
			super(name);
		}
		@Override
		public void run() {
			super.run();
			Looper.prepare(); // 准备Looper
			looper = Looper.myLooper(); // 获取到当前线程中的Looper对象
			Looper.loop();
		}

		Looper getLooper() {return looper;}	// 暴露获取当前线程Looper对象的方法
	}

但是这样又会存在一个问题,会存在并发的问题,外界线程和当前线程是同步执行,会导致外部线程已经执行完,当前线程还未获取到Looper对象。这就会造成外部获取到的当前Thread的Looper对象未null,解决方案我们接着往下看,系统已经为我们提供了一套完整的解决方案 -- HandlerThread.java

HandlerThread原理:如何安全拿到子线程Looper

源码先行:

Java 复制代码
HandlerThread.java
    // 线程run方法
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
		// 在外部调用HandlerThread子线程start方法时,Synchronized会将主线程挂起等待子线程锁代码块执行完成后再执行主线程  ,确保主线程中调用到的
		// Looper不为空
        synchronized (this) {
			// notifyAll();
            mLooper = Looper.myLooper();
            notifyAll();  // 通知挂起的线程可以执行了,也就是getLooper方法中的Looper可以返回了
			//code()
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();  //不会退出
        mTid = -1;
    }
    
    // 提供给外部的获取Looper的方法    
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

		// 在主线程中,如果子线程没有获取到CPU的执行时间片,主线程优先执行,就会在主线程中先执行到getLooper()方法上,这儿通过锁并挂起当前线程并释放锁,等待子线程获取到CPU
		// 执行时间片后在run方法中唤醒当前挂起的线程 再获取到对应的looper
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
        	// 为什么使用的是while循环 而不是用if循环
        	// 这是一种设计思想,原因: 如果在其他地方,调用了当前线程的notifyAll() 这就会导致当前挂起的线程恢复执行,返回当前的looper,但是可能当前的Looper还是null
        	// 如果采用if 只判断一次,可能会造成返回的looper还是null
            while (isAlive() && mLooper == null) {//?? if
                try {
                    wait();// 将线程挂起,并释放当前的锁给其他地方使用
                    //code()
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

逻辑执行流程: 1. 主线程调用handlerThread.start(),开启子线程 2. 主线程立刻调用getLooper(),此时子线程run还没执行完prepare()方法,mLooper还是null,主线程wait()阻塞。 3. 子线程run执行Looper.prepare(),拿到Looper赋值给mLooper,执行notifyAll()唤醒主线程 4. 主线程被唤醒后,退出while循环,返回已经初始化好的Looper,外部就可以正常创建Handler对象了。

总结

我们从Handler内存泄漏、主线程与子线程Handler差异入手,逐层拆解了解整套消息通信机制。

一条完整的消息流转链路: Handler.sendMessage() -> Message入队MessageQueue -> Looper不断调用queue.next()取出消息 -> 底层epoll机制实现空闲阻塞与消息唤醒 -> 最终交给Handler.handleMessage()处理 1. ThreadLocal:Looper和线程一一绑定,保证一个线程仅存在一个唯一的Looper 2. Message复用池(享元设计模式):复用Message对象,减少频繁创建销毁对象带来的GC开销 3. 消息屏障:框架内部机制,优先调度处理异步消息,支撑着Choreographer的UI绘制。 4. HandlerThread:系统提供的封装好的子线程Looper样板代码

Handler相关至此结束了,下一篇将开启Binder IPC通信机制的学习啦!

相关推荐
用户2181697049301 小时前
Kotlin 语法四 协程
android
stevenzqzq2 小时前
android 维度和渠道
android
xiaogai_gai2 小时前
生产汇报单查询同步实战:从金蝶云星空拉取到轻易云数据集成平台的工程化做法
android
释厄6233 小时前
错误无误公理——错误正确本同源都迭代定律——逆转有价值
android·开发语言·kotlin
wno7044 小时前
MYSQL 常用合并函数
android·数据库·mysql
事圆则缓4 小时前
HTTP 请求是怎么走的:从 Android 页面到服务端响应
android·网络协议·http
美狐美颜SDK开放平台6 小时前
直播APP平台搭建需要哪些技术?视频美颜SDK只是其中一环
android·ios·音视频·直播美颜sdk·视频美颜sdk
AIsoft_86896 小时前
传统记录方式为什么越来越难满足需求?
android·iphone
小π军7 小时前
安卓、iOS、鸿蒙三端的理解
android·ios·harmonyos