天天看点

关于 Handler 的灵魂三问

转载请注明出处:juejin.im/post/5c9896…

写在前面

之前在郭神的订阅号,看到了这一篇关于

Handler

的投稿文章

《或许你可以从这个角度去理解Handler》

介绍得很详细,分析源码的流程也很清晰。

Message

的对象获取方式,到

Handler

sendMessage

方法解析,再到

enqueueMessage

方法解析。

enqueueMessage

中没有看到回调方法

handleMessage

,结果是

dispatchMessage

方法调用了

handleMessage

再看

Handler

Looper

类的关系,又发现

loop

方法中调用了

dispatchMessage

,最后分析了

loop

方法,得出结论,

MessageQueue

消息队列最后是在这个方法执行的。

这位作者大佬也是事无巨细,在重要的方法和代码处都加了注释,使得读者理解起来更轻松,值得学习。

但由于个人理解能力有限,部分地方还存在疑惑,于是就自己动手丰衣足食,结合面试中可能会问到的一些问题来做了学习。

于是有了此文。

这是重点

1.Handler 的原理?

面试官最爱问的一个问题

首先我们了解一下

Handler

—— 处理者

Message

—— 消息

MessageQueue

—— 消息队列

Looper

—— 循环者

于是,我们大概可以这样描述:

消息处理者

Handler

从子线程中发送消息

Meesage

到 消息队列

MessageQueue

中,消息队列

MessageQueue

会对消息进行排序,循环者

Looper

循环地从消息队列

MessageQueue

中取出消息,回调给主线程中的消息处理者

Handler

, 在

handleMessage

方法处理结果。完成一次消息异步发送的流程。

然后,走一遍流程:

  • 回调方法,在此处理你要做的事情
public Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        //TODO..
    }
}
复制代码
           
  • 发送消息
handler.sendMessage(m);

handler.post(r);
复制代码
           
  • 发送消息最终调用的是这个方法
queue.enqueueMessage(msg, uptimeMillis);
复制代码
           
  • 消息队列

    MessageQueue

    中入队方法

    enqueueMessage()

    ,出队方法

    next()

boolean enqueueMessage(Message msg, long when) {...

Message next() {...
复制代码
           

next()

在循环者

Looper

loop

方法中被调用

获取到消息后,调用

dispatchMessage(msg)

来分发消息

public static void loop() {...

msg.target.dispatchMessage(msg);
...
复制代码
           

最终回到了 Handler 类中的

dispatchMessage

方法,如果

Message

是一个纯粹的消息体,那将会调用

handleMessage

方法。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码
           

到此,走完了这次流程。

我想你大概能知道,如何组织自己的语言回答面试官了。

但,可能多数面试官不止于此。

继续问道...

2.Handler 为什么可以 post Runnable?

这个问题,不太难理解,Ctrl+鼠标左键,看过源码的都能知道。我们跟着源码走一遍

handler.post(new Runnable() {...
复制代码
           

这里实际调用了

sendMessageDelayed(Message msg, long delayMillis)

Runnable

转成了

Message

对象,如何转换的呢

public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
复制代码
           

可以看到

Message

中的

callback

属性是一个

Runnable

对象

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
复制代码
           

3.Loop 死循环为什么不会导致主线程 ANR?

这个问题本身可能是有问题的...

  • 首先我们要知道

    loop

    方法中为什么要用死循环?

先说

ActivityThread

,虽然类名以

Thread

结尾,但它并没有继承

Thread

,它并不是一个线程类。

ActivityThread

Android

应用程序的入口,也就是任何一个进程的主线程入口。

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

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
复制代码
           

这就是一个Java 应用,

main

函数是入口,当执行到

loop

方法就开始死循环,如果死循环结束,那将会报错。

可以看到就是这样设计的。目的就是为了我们点击应用的图标时不会一闪而过,而是正确地打开页面,执行生命周期,响应各种输入事件。

我们的代码就是在主线程

loop

函数中的死循环中被执行的,所以这和 ANR 是两个不同的东西。

至于

Looper

会被阻塞,这个更深入一些,不在这里讨论。

  • 什么情况下会发生ANR?

1.输入事件(按键和触摸事件)5s内没被处理。

2.

BroadcastReceiver

的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s)。

3.

service

前台20s后台200s未完成启动。

4.

ContentProvider

publish

在10s内没进行完。

所以它不包括

Looper.loop()

的死循环。

写在后面

一直想总结一下自己对

Handler

的认识,以增强记忆,帮助学习和理解。

直到今天才完成。

关于

Handler

,它实现了线程之间的通信,而线程只是CPU调度的最小单位。了解了

Handler

的原理只是看到冰山一角。要想了解更多的通信方式,还有很长的路要走。

Linux已经拥有的进程间通信IPC手段包括(Internet Process Connection): 管道(Pipe)、信号(Signal)和跟踪(Trace)、插口(Socket)、报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)。而 Binder 是主要的IPC方式。

记录在此,仅为学习!

感谢您的阅读!欢迎指正!

参考:

1.yanzhenjie.blog.csdn.net/article/det…

2.www.zhihu.com/question/34…

3.www.jianshu.com/p/fa962a5fd…

转载于:https://juejin.im/post/5c9896ca6fb9a070f30b0e18