Handler浅析

Handler 相信每一个做 Android 开发的小伙伴都非常熟悉了,最常用的场景就是在子线程中进行数据操作然后通过 Handler 消息机制通知到 UI 线程来更新 UI ,地球人都知道在子线程中更新 UI 一般情况下都会报错。是吧!咱用的贼溜,各种姿势发送 Handler 消息都熟练掌握,但是如果这时候出去面试被问到“ Handler 原理”,“消息是怎么从子线程发送到主线程的”等等 Handler 底层的实现,就懵逼了。

虽然网上关于分析 Handler 的博客文章非常多,已经有很多大佬分析的非常透彻清楚了,但我这里还是想在看过大佬们的文章后自己再写一篇,一方面是让自己加深理解,另一方面就是想分享所学知识(分享会让知识变的更多)。

看了很多大佬去大厂面试的面经,Handler几乎是必问的,所以我们更加必须知其所以然了。

一、Handler是啥

1.1 一句话描述 Handler

简单来说Handler是结合线程消息队列来发送和处理 MessageRunnable 对象来实现线程间通信的工具。

1.2 典型实例

先来一起看一个关于 Handler 使用的典型实例(实例来源 Gityuan 博客)


class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        Looper.prepare();

        mHandler = new Handler() { 

            public void handleMessage(Message msg) {

                //TODO 定义消息处理逻辑.

            }

        };

        Looper.loop(); 

    }

}

在线程中声明一个Handler必须按以下顺序三步走:

  1. 调用 Looper.prepare() 。

  2. new Handler() 。

  3. 调用 Looper.loop() 。

以上三步曲我称之为:Looper 肉夹馍 (手动狗头)

这里产生一个问题了:

Question 1: 创建 Handler 之前为啥一定要先调用 Looper.prepare() 呢?

我们带着这个问题继续往下看。

二、Handler 工作原理

说起 Handler 那就必须要了解 Android 的消息机制了, Android 中有大量的交互场景是通过消息机制来驱动的,如我们最熟悉不过的 Activity 的生命周期等等。而 Handler 就是 Android 消息机制的重要组成部分。

我们先来看一张图:

image

图中描述的是消息是怎样从子线程流到主线程从而完成线程间通信的大概流程。Android 整个消息机制主要由Handler,Looper,MessageQueue,Message四个部分组成。这里先简单介绍一下这四位具体职责:

  • Handler : 负责发送消息和接收消息并进行处理。

  • MessageQueue : 消息队列,负责消息存储与管理。

  • Looper : 负责关联 Handler 当前所处线程,进行消息分发,通过不断循环实现将消息从消息队列取出,然后分发给对应的消息处理者。

  • Message : 消息载体。

2.1 原理解析

看完上图脑海中应该对 Android 的消息机制已经有了一个大概的概念了,下面我们开始从源码来看 Android 的消息机制具体实现。这里就以上图中的主线程和子线程通信为思路。

先来看上图对应的代码实例


public class MainActivity extends AppCompatActivity {

    private Handler handler = new Handler(){

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            //todo UI线程接收消息并处理

            Log.d("MainActivity","handleMessage....");

        }

    };

    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        new Thread(){

            @Override

            public void run() {

                super.run();

                //子线程发送消息

                handler.sendMessage(new Message());

            }

        }.start();

    }

}

2.1.1 创建 Handler

主( UI )线程中实例化一个 Handle 对象。我们跟到源码中看一下 Handle()做了什么事情。

  • Handle 默认构造方法

public Handler() {

    this(null, false);

}

  • 默认构造方法最终会走到 Handler(Callback callback, boolean async)

public Handler(Callback callback, boolean async) {

    ......

    //获取当前Looper对象

    mLooper = Looper.myLooper();

    //检查当前线程Looper是否存在

    if (mLooper == null) {

        throw new RuntimeException(

            "Can't create handler inside thread " + Thread.currentThread()

                    + " that has not called Looper.prepare()");

    }

    mQueue = mLooper.mQueue;

  ......

}

源码解析:

在该构造方法中,每次实例化 Handler 都会检查当前线程是否已绑定 Looper ,如果没有则会抛出异常提示你没有调用 Looper.Prepare()。看到这里【1.2】的 Question 1`是不是就有答案了。

Question 1 正解:

在创建 Handler 对象之前必须要先创建 Looper 并且将 Looper 对象绑定到当前线程,否则在实例化 Handler 时会报异常。

2.1.2 创建 Looper

那么 Looper.Prepare() 方法里又是怎么创建 Looper 对象并绑定到当前线程的呢?跟进源码看一下。


public static void prepare() {

    prepare(true);

}

private static void prepare(boolean quitAllowed) {

    //检查当前线程是否已有 Looper,每个线程只允许执行一次该方法,第二次执行时线程的TLS已有数据,则会抛出异常。

    if (sThreadLocal.get() != null) {

        throw new RuntimeException("Only one Looper may be created per thread");

    }

    //创建Looper对象,并保存到当前线程的TLS区域

    sThreadLocal.set(new Looper(quitAllowed));

}

源码解析:

  • 调用无参 prepare() 方法默认会调用 prepare(true)

prepare(boolean quitAllowed) 方法参数 quitAllowed 表示是否当前 Looper 是否可退出,传 true 表示可退出,false 为不可退出。

  • prepare(boolean quitAllowed) 方法中会先检查当前线程是否已经有 Looper ,如果有则会抛出异常,也就意味着每个线程中 Looper.Prepare()方法只可以调用一次!

这里我们就知道了,Looper 和当前线程是一一对应的关系,一个 Looper 只能关联一个线程。

  • 检查完当前线程是否已经有 LoopersThreadLocal.set(new Looper(quitAllowed)) 就开始创建一个 Looper 对象并保存到当前线程的TLS(关于 ThreadLocal 请自行查阅资料)区域,这样就完成了 Looper 对象的创建和关联到当前线程。

Looper.Prepare()调用后 Looper 创建好了,我们再回到 Handler 构造方法中:

Looper.myLooper()方法


  public static @Nullable Looper myLooper() {

        return sThreadLocal.get();

  }

源码解析:

这个方法很简单就是去当前线程的中的 TLS 区域 Get Looper 对象,没错就是 Looper.Prepare()方法中sThreadLocal.set(new Looper(quitAllowed)) Set 进去的。

2.1.3 创建 MessageQueue

最后 mQueue = mLooper.mQueue 会获取 Looper 对象的消息队列,如果你刚才从 prepare(boolean quitAllowed) 方法中继续跟进 new Looper(quitAllowed)方法中你就会发现:


private Looper(boolean quitAllowed) {

    mQueue = new MessageQueue(quitAllowed);

    mThread = Thread.currentThread();

}

创建 Looper 对象时会为当前 Looper 对象创建一个消息队列 new MessageQueue(quitAllowed)

2.1.4 强行Question 2

不知道细心的你发现了没,我们的图一的代码实例中是不是少了点什么。

没错,少了肉夹馍!

image

呐!Question 2来啦:

不是反复强调创建 Handler 之前要先调用 Looper.Prepare()创建 Looper 吗?那图一的代码实例中主线程并没有调用啊,直接就创建了 Handler ,你这代码肯定会抛这个异常:


RuntimeException( "Can't create handler inside thread " + Thread.currentThread()

                    + " that has not called Looper.prepare()");

悄咪咪告诉你,如果你在图一的代码实例中创建Handler 之前调用 Looper.Prepare()又会抛出以下异常哦:


RuntimeException("Only one Looper may be created per thread");

image

答案在 ActivityThread.main() 中。

ActivityThreadmain()方法就是整个APP的入口,也就是我们通常所讲的主线程UI线程

但他实际上并不是一个线程,ActivityThread 并没有继承 Thread 类,我们可以把他理解为主线程的管理者,负责管理主线程的执行,调度等操作,可以看下这个类源码的注释。


/**

* This manages the execution of the main thread in an

* application process, scheduling and executing activities,

* broadcasts, and other operations on it as the activity

* manager requests.

*

* {@hide}

*/

public final class ActivityThread extends ClientTransactionHandler {

    ......

}

好了,我们来看下 ActivityThread.main() 一探究竟(这里只贴关键代码了)。


public static void main(String[] args) {

        .......

        Looper.prepareMainLooper();

        .......



        ActivityThread thread = new ActivityThread();

        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {

            sMainThreadHandler = thread.getHandler();

        }

        if (false) {

            Looper.myLooper().setMessageLogging(new

                    LogPrinter(Log.DEBUG, "ActivityThread"));

        }

        .......

        Looper.loop();

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

    }

源码解析:

  1. Looper.prepareMainLooper();

    此方法就是为我们的主线程创建了 Looper,内部实现调用了 prepare(false)方法。传 false 表示当前 Looper不可退出。

    
      /**
    
    * Initialize the current thread as a looper, marking it as an
    
    * application's main looper. The main looper for your application
    
    * is created by the Android environment, so you should never need
    
    * to call this function yourself.  See also: {@link #prepare()}
    
    */
    
    public static void prepareMainLooper() {
    
        prepare(false);
    
        synchronized (Looper.class) {
    
            if (sMainLooper != null) {
    
                throw new IllegalStateException("The main Looper has already been prepared.");
    
            }
    
            sMainLooper = myLooper();
    
        }
    
    }
    
    

以上,就是Question 2的正解所在,Android 程序入口 ActivityThread.main() 中调用 Looper.prepareMainLooper(); 为主线程创建了 Looper,所以我们的主线程可以直接创建 Handler

  1. Looper.loop();

和【1.2】中的实例一样,标准的 Looper 肉夹馍写法,这个方法干啥用的,后面再说。

2.1.5 小结

OK,到这里,Handler,Looper,MessageQueue 都已经准备就绪,图一的流程差不多可以跑起来了。我们总结一下重点:

  1. 初始化 Handler 会检测当前线程 Looper 是否存在,没有则会抛出异常。

  2. 基于第一点,所以每次创建 Handler 之前必须先调用 Looper.Prepare() 创建 Looper

  3. Looper 可以理解为当前线程的消息调度者,负责消息分发。和当前线程的关联绑定通过 sThreadLocal.set(new Looper(quitAllowed)); 实现。

  4. Looper 和当前线程是一对一的关系, Looper.Prepare()方法中有进行校验,重复创建 Looper 绑定会抛出异常。

  5. LooperMessagQueue 也是一对一的关系,构造 Looper 对象时会为其创建一个对应的 MessagQueue 对象。

2.2 消息发送 - Handler

Handler 消息发送其实是一个消息入队的过程,调用 Handler 相应的发送消息方法将当前消息添加到 MessageQueue 消息队列,最终由 Looper 负责进行消息分发。

Handler 为我们提供了一系列的消息发送方法:


public final boolean sendMessage(Message msg){

    return sendMessageDelayed(msg, 0);

}

public final boolean sendEmptyMessage(int what){

    return sendEmptyMessageDelayed(what, 0);

}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {

    Message msg = Message.obtain();

    msg.what = what;

    return sendMessageDelayed(msg, delayMillis);

}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {

    Message msg = Message.obtain();

    msg.what = what;

    return sendMessageAtTime(msg, uptimeMillis);

}

发送消息的方法还有很多就不一一列出来了,所有发送消息的方法最终都是调用到 enqueueMessage 方法中:

image

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

    msg.target = this;

    if (mAsynchronous) {

        msg.setAsynchronous(true);

    }

    return queue.enqueueMessage(msg, uptimeMillis);

}

该方法实现最终调用的是queue.enqueueMessage(msg,uptimeMillis)方法即 MessageQueue 添加消息到消息队列中。

2.3 消息管理与存储 - MessageQueue

前面提到 Handler 所有发送的消息最终都通过 enqueueMessage 加入到 MessageQueue 消息队列中,所以消息的存储与管理由 MessageQueue 来负责。

2.3.1 消息入队

消息入队由 enqueueMessage(msg,uptimeMillis) 方法负责:


boolean enqueueMessage(Message msg, long when) {

    //验证Message 是否有target,这里的target就是对应的handler

    if (msg.target == null) {

        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;

        Message p = mMessages;

        boolean needWake;

        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 {

            //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非

            //消息队头存在barrier,并且同时Message是队列中最早的异步消息。

            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;

}

消息入队顺序是按照 Message 触发时间 long when入队的,有新消息加入时,会循环遍历当前消息队列,对比消息触发时间,直到找到当前消息合适的插入位置,以此来保证所有消息的触发时间顺序。

2.3.1 消息出队

MessageQueuenext 方法负责从消息队列中取出一条消息。


Message next() {

    .......



    for (;;) {

        if (nextPollTimeoutMillis != 0) {

            Binder.flushPendingCommands();

        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {

            // 尝试检索下一条消息。如果找到,则返回。

            final long now = SystemClock.uptimeMillis();

            Message prevMsg = null;

            Message msg = mMessages;

            if (msg != null && msg.target == null) {

                ////当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。

                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;

                    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; //成功地获取MessageQueue中的下一条即将要执行的消息

                }

            } else {

                //没有消息

                nextPollTimeoutMillis = -1;

            }

            //消息正在退出,返回null

            if (mQuitting) {

                dispose();

                return null;

            }

        }



        //IdleHandler相关内容

        .......

    }

}

2.4 消息分发 - Looper

有人( Handler )在子线程把消息发到我(MessageQueue)这里来了,我负责把消息存起来,并且告诉大家存储(enqueueMessage)消息和提取(next)消息的方法 ,那么谁来负责把我这里的消息分发出去并且告诉主线程的人(Handler)呢?

干这个活的就是我们的 Looper 了!

还记得大明湖畔的的 Looper 肉夹馍 么!

现在我们就来看一下 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;

    ......

    //开启死循环

    for (;;) {

        //循环遍历,不断从MessagQueue中获取Messag

        Message msg = queue.next(); // might block

        if (msg == null) {

            // No message indicates that the message queue is quitting.

            return;

        }

        ......



        try {

            //分发消息到对应的msg.target

            msg.target.dispatchMessage(msg);

            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

        } finally {

            if (traceTag != 0) {

                Trace.traceEnd(traceTag);

            }

        }

      ......

        msg.recycleUnchecked();

    }

}

源码解析:

调用 loop() 方法后,方法内部会开启一个死循环,通过调用 MessageQueue 消息出栈方法 next() 获取 Message,然后调用 msg.target.dispatchMessage(msg); 将消息分发到 Message 对应的 target

msg.target 就是发送该消息的 Handler 对象。

2.5 消息接收 - Handler

loop() 方法中调用 msg.target.dispatchMessage(msg);将消息分发出去,我们来看一下分发出去的消息是怎么被接收的。


/**

* Handle system messages here.

*/

public void dispatchMessage(Message msg) {

    if (msg.callback != null) {

        handleCallback(msg);

    } else {

        if (mCallback != null) {

            if (mCallback.handleMessage(msg)) {

                return;

            }

        }

        //回调到 Handler 的 handleMessage 方法

        handleMessage(msg);

    }

}

源码解析:

dispatchMessage(msg) 方法内部调用了 handleMessage(msg)方法,这个方法用过 Handler 的应该都非常熟悉了,它是 Handler 的一个空实现方法,一般在创建 Handler 的线程重写此方法,就可以回调到子线程发出的消息了。


/**

* Subclasses must implement this to receive messages.

*/

public void handleMessage(Message msg) {

}



三、 总结

  1. Looper 肉夹馍 职责:
  • 为当前线程创建 Looper 对象,并关联当前线程。

  • 为当前线程创建 Handler 对象。

  • 开启循环,不断从 MessageQueue 消息队列中取消息,取到后分发给 Message 对应的 target

  1. 整个消息从子线程流转到主线程流程:
  • 主线程使用 Looper肉夹馍

  • 子线程调用 Handler 相应发送消息方法,最终通过 enqueueMessage 将消息加入消息队列 MessageQueue

  • Looper 循环从 MessageQueue 获取到消息,调用当前消息对应的 targetdispatchMessage(msg) 方法。

  • dispatchMessage(msg) 回调 handleMessage(msg) 方法,消息即从子线程流转到重写 handleMessage(msg)方法所在线程。

  1. Android 主线程默认会使用 Looper 肉夹馍(详见 ActivityThread.main()),因此 Android 主线程中只需创建 Handler 对象即可。

  2. 主线程 Looper 不可以退出,因此 ActivityThread.main() 方法中调用的 Looper.prepareMainLooper(); 方法中 prepare(false); 传参是 false

如果强行退出主线程 Looper 会抛出以下异常:


  Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.

  at android.os.MessageQueue.quit(MessageQueue.java:415)

  at android.os.Looper.quit(Looper.java:240)

原因是:Android App 主进程依赖消息机制驱动,主线程消息循环退出了,那么 App 也会退出。

四、问题思考

  1. Looper 中开启了一个死循环来从 MessageQueue 中取消息,而且没有单独开线程,为什么不会造成 Android 主线程卡死 ANR?

[ 问题答案可以看 Gityuan 大佬的回答

](https://www.zhihu.com/question/34652589)

五、参考资料

Android消息机制1-Handler(Java层)

Handler 都没搞懂,拿什么去跳槽啊?

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

推荐阅读更多精彩内容