Android中Handler原理分析

1. 介绍

做Android开发一定会用到Handler,通常是用来将一些子线程中得到的结果post到主线线程,即子线程与主线程的切换和通信。一些第三方的库和官方库涉及到线程的基本上也是用的Handler,例如:EventBus(3.3.1),LiveData(2.3.1)。可以说只要有异步线程与主线程通信的操作基本上都是通过Handler实现。

那么,Handler得到如此青睐的背后是无奈的选择还是另有其因,本文带你一探究竟。

2. Handler的使用

使用Handler来发送一个Message或Runnable来处理业务(Runnable会被封装成Message),调用hander.post(...)方法在某个线程并不意味着处理就在哪个线程,在哪个线程处理的关键在于创建Handler时传入的参数Looper是哪个线程的,若入参Looper是Looper.getMainLooper(),则在主线程。

示例代码:

// 示例类
class Xxx{
    // 主线程 Hanlder
    fun aMethod(){
        // 主线程的handler
        val handler = Handler(Looper.getMainLooper())
        // 发送消息
        handler.sendEmptyMessage(1)
        // post Runnable
        handler.post {
            // do something
            // 这里的逻辑会在主线程
        }
    }
    // 子线程 Handler
    fun bMethod(){
        // 开启一个线程
        thread {
            // 初始化Looper;若不调用该方法Looper.myLooper()将为空
            Looper.prepare()
            // 初始化Handler
            val handler = Looper.myLooper()?.let { Handler(it) }
            handler?.sendEmptyMessage(1)
            handler?.post {
                // do something... 这里的逻辑会在子线程
                // 退出looper,释放线程
                Looper.myLooper()?.quit()
                
            }
            // looper开始死循环
            Looper.loop()
            // 退出looper的逻辑写在这里没有效果,上面loop是死循环,不会执行到这里
            // Looper.myLooper()?.quit()
        }
    }
    // 常用写法
    fun cMethod(){
        thread {
            val handler = Handler(Looper.getMainLooper())
            handler.sendEmptyMessage(1)
            handler.post {
                // do something
                // 这里的逻辑会在主线程
            }
        }
    }
}

分析一下异同点:

  • a 和 b

    相同点:基本没有

    不同点:

    1. aMethod 在主线程直接调用;bMethod新开一个线程调用
    2. aMethod 没用调用Looper额外方法;bMethod调用了Looper.prepare()Looper.loop()Looper.myLooper()?.quit()
    3. aMethod 的Handler的入参是Looper.getMainLooper();bMethod是Looper.myLooper()
    4. aMethod 的逻辑在主线程;bMethod逻辑在子线程
  • a 和 c

    相同点:

    1. Handler的入参是Looper.getMainLooper()
    2. 没用调用Looper额外方法
    3. 逻辑都是在主线程

    不同点:

    1. aMethod在主线程直接调用;cMethod新开一个线程调用
  • b 和 c

    相同点:

    1. 都是新开一个线程调用

    不同点

    1. bMethod调用了Looper.prepare()Looper.loop()Looper.myLooper()?.quit();cMethod没用调用Looper额外方法
    2. bMethod是Looper.myLooper();cMethod的Handler的入参是Looper.getMainLooper()
    3. bMethod逻辑在子线程;cMethod的逻辑在主线程

提问:

  1. aMethod在主线调用,bMethod新开一个线程,cMethod新开一个线程,为什么只有bMethod的逻辑是在子线程?

  2. bMethod和cMethod都是新开一个线程,为什么bMethod需要调用Looper.prepare()等方法而cMethod不需要?

分析:

  1. 只有bMethod在创建Handler时入参是Looper.myLooper(),其余都是Looper.getMainLooper()myLooper()表示当前线程的Looper实例,而getMainLooper()则是主线程中的Looper实例,故只有bMethod是在子线程中处理逻辑。
  2. bMethod和cMethod都是新开一个线程,bMethod需要调用Looper.prepare()而cMethod不需要,关键还是在于创建Handler时入参不同。如果bMethod不调用Looper.prepare()Looper.myLooper()为空,会报错;而cMethod直接调用Looper.getMainLooper()不会报错,是因为在主线程中已经创建好了Looper实例

3. 原理分析

分析基于Android api-31,不同版本源码可能有差异。分析之前思考几个问题:

  1. Handler是如何切换线程的?
  2. Looper的作用是什么?一个线程有几个Looper?
  3. 为什么在Handler初始化时要传递Looper,与Looper是什么关系?
  4. Handler发送的Message是怎么处理的?

3.1 涉及到的相关类

按照惯例,上类图:

HandlerUML.png
  • Message:Handler中被传递的数据对象

  • MessageQueue:负责管理Message的队列,真实的实例是存在Looper中,Handler中的mQueue实际上是在初始化的时候指向了Looper中的实例

  • Looper:负责管理MessageQueue,调用looper是开启一个死循环,不停的从MessageQueue中获取Message来处理直到调用了quit()

  • Handler:主角,内部有一个指向Looper维护的MessageQueue的实例,在sendMessage或者post Runnable时实际上都是向该队列中添加Message

从上面的分析了解到,实际上完整的Handler的使用是:

Looper.prepare()
val handler = Looper.myLooper()?.let { Handler(it) }
handler?.post {
    // do something
    Looper.myLooper()?.quit()
}
Looper.loop()

接下来就根据使用流程分析一下源码中的实现。

3.2 创建Looper

Looper的实例创建是通过静态方法prepare()实例化的,构造方法是private的:

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    final MessageQueue mQueue;
    final Thread mThread;
    // 私有构造方法
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    // 为当前线程初始化 Looper
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 如果当前线程有looper,则抛异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 直接new一个Looper并放到sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
}

prepare(boolean quitAllowed)中可以看出,一个线程只能有1个Looper实例。通过prepare()方法为当前线程创建来唯一的一个Looper,就可以进行下面的流程了。

3.3 创建Handler

/**
 * handler构造方法中 没有 Looper的入参都被标记过时了
 * 官方也在提醒开发者显示的指明looper,更容易区分handler在哪个线程使用
 */
public class Handler {
    // 通过注解 @NonNull 来限制入参不能为空
    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }
    // 该构造方法可以传入一个 callback ,在处理消息时,callback的优先级高于子类实现的handleMessage(msg)方法
    public Handler(@NonNull Looper looper, @Nullable Callback callback) {
        this(looper, callback, false);
    }
    // 给成员变量赋值
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        // 这里将成员变量mQueue指向了looper中的mQueue,故在handler的中操作mQueue也就是操作looper中的mQueue
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    // 子类实现处理消息的逻辑,优先级低于 mCallback
    public void handleMessage(@NonNull Message msg) {
    }

}

在Handler中的构造方法中Looper参数是不能为空的,因此可以反推,在创建Handler实例时必须先调用Looper.prepare()方法为线程初始化Looper实例。

3.4 Handler发送Message

Handler提供了一系列发送消息的方法,例如send()和post()。不管哪种方法最终都会走到Handler.enqueueMessage(MessageQueue, Message, long)。例如:

Handler.post(Runnable)

-> sendMessageDelayed(Message, long)

  -> sendMessageAtTime(Message, long)
                  
      -> enqueueMessage(MessageQueue, Message, long)  > return queue.enqueueMessage(msg, uptimeMillis);

enqueueMessage(MessageQueue, Message, long)queue中,通过前面介绍也就是Looper的mQueue中。

至此,Handler的使命完成了一半,即将待处理的Message添加到Looper中的mQueue中。

Tips:Handler在发送消息时,代码一定是写到Looper.loop()之前,loop()方法是死循环,后面的代码都不会执行。

看一下Message相关源码:

public final class Message implements Parcelable {
    // 当前 message 对应的目标处理者,即处理当前message的 handler
    Handler target;
    // Message处理消息的回调,优先级最高
    Runnable callback;
}

Message有一个Handler的成员变量,表示可以处理当前messagehandler实例,上面分析到handler最后都会调用enqueueMessage(...)方法:

public class Handler {
    // 向队列中添加 Message
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                                   long uptimeMillis) {
        // message 的 taget 赋值
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 将消息添加到队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

enqueueMessage(...)中可以看到被发送的消息的target实例被赋值为this,即谁发送的消息谁处理。

3.5 Message的分发与处理

Looper创建好了,Message也被添加到MessageQueue中,接下来看一下Message是怎么处理的。

上面分析到Looper最后需要调用Looper.loop()

public final class Looper {
    
    /**
     * 在当前线程启动消息对象。调用 quit() 退出循环
     */
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    public static void loop() {
        // 当前的looper
        final Looper me = myLooper();
        // 为空抛异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // other code ...
        // 死循环开始
        for (;;) {
            // 如果为 false 则退出循环
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
    // 处理一次消息
    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;
        }
        // other code ...
        try {
            // 调用 msg 持有的 handler 的 dispatchMessage 方法
            msg.target.dispatchMessage(msg);
            // other code ...
        } catch (Exception exception) {
            // other code ...
            throw exception;
        } finally {
            // other code ...
        }
        // other code ...
        // 回收消息
        msg.recycleUnchecked();
        return true;
    }
    // 退出循环
    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
}

loop()方法开启了循环,真正分发消息是在loopOnce()方法,在经过一系列判断后会调用msg.target.dispatchMessage(msg),该方法即是回调到了Handler的dispatchMessage(msg),消息交给Handler处理。那么msg是从哪里来的呢?第一行代码me.mQueue.next(),从消息的队列里取到下一个消息:

public final class MessageQueue {
    // 标记队列是否退出
    private boolean mQuitting;
    // 从对列中取下一个待处理的消息
    Message next() {
        // other code ... 
        // 开启死循环
        for (;;) {
            // other code ...
            synchronized (this) {
                // other code ...
                if(...){
                    // 返回待处理消息
                    return msg;                    
                }
                // other code ...
                // 如果是推出来,则返回为空
                if (mQuitting) {
                    dispose();
                    return null;
                }
                // other code ...
            }
            // other code ...
        }
    }

    // 退出
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            // 改变退出状态
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
}

可以看到,在取下一个消息时也是死循环,且只有一种情况会返回为空,即mQuitting == true时,当前队列需要推出时返回为空。结合外层的判断理解,当MeeageQueue.next()为空时Looper.loopOnce(...)flaseLooper.loop()也会退出循环。为什么会有2个死循环呢,一个行不行?

分析一下每个死循环的作用,首先MeeageQueue.next()的死循环是一直需要从MessageQueue中取消息,如果没有就一直循环等到有消息了就返回,队列中是不确定有多少个消息的,即是当前没有也会有再添加的,所以除非主动退出,否则就会一直循环下去找到消息为止。而Looper.loop()的死循环是处理完一个消息,接着处理下一个,同样Looper也是不确定是否还有下一个待处理的消息,也就会一直循环下去。

MeeageQueue.next()的死循环作用是查找到消息为止;Looper.loop()的死循环作用是不停的处理消息。

那么Looper.loop()什么时候退出呢?首先主线程的Looper是不能退出的,退出就会抛异常【见3.6】,其他线程Looper可以调用Looper.quit()Looper.quitSafely()来退出循环。其原理就是改变队列里的mQuitting的值。

通过调用msg.target.dispatchMessage(msg)已经把消息交给了Handler处理:

public class Handler {
    // handler处理消息的回调
    public interface Callback {
        boolean handleMessage(@NonNull Message msg);
    }

    // 子类实现该方法处理消息
    public void handleMessage(@NonNull Message msg) {
    }

    /**
     * 在这里处理消息
     */
    public void dispatchMessage(@NonNull Message msg) {
        // 回调message的runnable
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // handler成员变量 Callback mCallback 处理消息;mCallback是在handler构造函数中赋值的
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // handler处理消息的方法
            handleMessage(msg);
        }
    }

    // 通过message的callback处理消息
    private static void handleCallback(Message message) {
        message.callback.run();
    }
}

可以看到有3个都处理了消息,但是消息只会被处理1次,其优先级为:消息自带Runnable处理 > handler成员变量Callback 处理 > handler方法处理。

3.6 小结

  • MainLooper初始化是在ActivityThread中
public final class ActivityThread {
    public static void main(String[] args) {
        // other code ...
        // 初始化 mainLooper,内部会调用 prepare(boolean) 方法,并给成员变量 sMainLooper 赋值
        Looper.prepareMainLooper();
        // other code ...
        // 开始死循环
        Looper.loop();
        // loop退出则抛异常
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

看一下一开始的问题:

  1. Handler是如何切换线程的?

    通过Looper切换线程,初始化handler是传递的Looper决定来处理消息时的线程。

  2. Looper的作用是什么?一个线程有几个Looper?

    Looper通过循环从MessageQueue中取出Message交给handler处理。1个线程有且只有1个Looper

  3. 为什么在Handler初始化时要传递Looper,与Looper是什么关系?

    Handler内部的MessageQueue需要指向Looper中的MessageQueue,这样就可以间接向Looper中的MessageQueue中添加消息,而Looper在哪个线程取出消息和处理消息的操作就在哪个线程,故Handler是需要通过Looper实现切换线程的操作。因为Handler持有Looper的成员变量,故与Looper是关联关系,而且Handler无法脱离Looper单独工作,所以也可以说是组合关系(特殊的关联关系的一种)。

4. 常见问题

  1. Activity中使用匿名内部类或非静态内部类,会造成内存泄漏吗?如果会,为什么?
    答:会造成内存泄漏,匿名内部类和非静态内部类会持有外部类的引用即Activity,当Activity销毁触发垃圾回收(GC)时,若此时Handler正在处理消息,则不会被GC机制回收,故Activity会发生泄漏。

解决:正常使用外部类继承Handler、静态内部类(还可使用WeakReference弱引用持有外部类)、外部类生命周期结束时清空消息队列(removeCallbacksAndMessages(null)

2.Android中为什么主线程不会卡死

5. 总结

  • Handler负责添加和处理消息;Message负责携带信息是被处理的;MessagQueue负责管理消息;Looper负责分发消息;
  • 主线程Looper不能退出,退出则报异常;
  • 子线程Looper需要自己调用Looper.myLooper().quit()方法退出;
  • 子线程在创建Handler传入Looper.myLooper()时需要先创建Looper;
  • 主线的Looper是在启动时ActivityThread中创建的;
  • Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,并不是创建 Handler 的线程;

参考:

Handler 都没搞懂

Android中为什么主线程不会卡死
Android异步通信:详解 Handler 内存泄露的原因

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容