Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >面试官还问Handler?那我要给你讲个故事

面试官还问Handler?那我要给你讲个故事

作者头像
没关系再继续努力
发布于 2021-12-25 07:05:59
发布于 2021-12-25 07:05:59
46600
代码可运行
举报
文章被收录于专栏:Android面试Android面试
运行总次数:0
代码可运行

来吧小兄弟,说说Handler怎么回事

Handler的相关博客太多了,随便一搜都一大把,但是基本都是上来就贴源码,讲姿势,短时间不太好弄明白整体的关系,和流程.

面试官,你坐好,听听我这个故事吹的怎么样?

本文就以生活点餐的例子再结合源码原理进行解析。希望对你有一点帮助。 来,咱们进入角色。

Handler,Looper,MessageQueue,Message的全程协作的关系就好比一个餐厅的整体运作关系

  • Handler好比点餐员
  • Looper好比后厨厨师长。
  • MessageQueue好比订单打单机。
  • Message好比一桌一桌的订单。

接下来我们回顾下我们餐厅点餐的场景,餐厅点餐分为标准点餐和特殊点餐,我们分解来看。

标准流程

  1. 首先进入一家店,通过点餐员点餐把数据提交到后厨打单机。
  2. 然后厨师长一张一张的拿起订单,按照点餐的先后顺序,交代后厨的厨师开始制作。
  3. 制作好后上菜,并标记已做好的订单。

特殊流程

  1. 订单为延迟订单,比如客人要求30分钟后人齐了再制作,这时会把该订单按时间排序放到订单队列的合适位置,并通过SystemClock.uptimeMillis()定好闹铃。至于为什么用uptimeMillis是因为该时间是系统启动开始计算的毫秒时间,不受手动调控时间的影响。
  2. 如果打单机中全是延迟订单,则下令给后厨厨师休息,并在门口贴上免打扰的牌子(needWake),等待闹铃提醒,如有新的即时订单进来并且发现有免打扰的牌子,则通过nativeWake()唤醒厨师再开始制作上菜。
  3. 但是为了提升店铺菜品覆盖,很多相邻的店铺都选择合作经营,就是你可以混搭旁边店的餐到本店吃,此时只需点餐员提交旁边店的订单即可,这样旁边店的厨师长就可以通过打单机取出订单并进行制作和上菜。

总结

一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。

映射到以上场景中

  • 一家店就好比一个Thread
  • 而一个Thread中可以有多个Handler(点餐员)
  • 但是一家店只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。

面试官,我差不多吹完了,你要还不信,那就不好意思了?

根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。

Looper的工作流程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ActivityThread.main();//初始化入口
    1. Looper.prepareMainLooper(); //初始化
          Looper.prepare(false); //设置不可关闭
              Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
                    1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
                    1.2.Looper.mThread = Thread.currentThread();
    2. Looper.loop();
        2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
              nativePollOnce(); //阻塞队列
                  native -> pollInner() //底层阻塞实现
                        native -> epoll_wait();
        2.2.Handler.dispatchMessage(msg);//消息分发

myLooper().mQueue.next()实现原理

  1. 通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
  2. 同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
  3. 可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。

Handler.dispatchMessage(msg)实现原理

  1. 优先回调msg.callback。
  2. 其次回调handler构造函数中的callback。
  3. 最后回调handler handleMessage()。

Hander发送消息的流程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.Handler handler = new Handler();//初始化Handler
        1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
        2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。

2.handler.post(Runnable);//发送消息
        sendMessageDelayed(Message msg, long delayMillis);
            sendMessageAtTime(Message msg, long uptimeMillis);
                Handler.enqueueMessage();//Message.target 赋值为this。
                    Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。

MessageQueue.enqueueMessage()方法实现原理

  1. 如果消息队列被放弃,则抛出异常。
  2. 如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
  3. 如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。

接下来该面试官问了

经常有人问为什么主线程的Looper阻塞不会导致ANR?

  1. 首先我们得知道ANR是主线程5秒内没有响应。
  2. 什么叫5秒没有响应呢?Android系统中所有的操作均通过Handler添加事件到事件队列,Looper循环去队列去取事件进行执行。如果主线程事件反馈超过了5秒则提示ANR。
  3. 如果没有事件进来,基于Linux pipe/epoll机制会阻塞loop方法中的queue.next()中的nativePollOnce()不会报ANR。
  4. 对于以上的例子来说,ANR可以理解为用户进行即时点餐后没按时上菜(当然未按时上菜的原因很多,可能做的慢(耗时操作IO等),也可能厨具被占用(死锁),还有可能厨师不够多(CPU性能差)等等。。。),顾客发起了投诉,或差评。但如果约定时间还没到,或者当前没人点餐,是不会有差评或投诉产生的,因此也不会产生ANR。

以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子

  • 1.为什么子线程不能直接new Handler()?
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
       new Thread(new Runnable() {
           @Override
           public void run() {
              Handler handler = new Handler();
           }
       }).start();


       以上代码会报以下下错误

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
       at android.os.Handler.<init>(Handler.java:207)
       at android.os.Handler.<init>(Handler.java:119)
       at com.example.test.MainActivity$2.run(MainActivity.java:21)
       at java.lang.Thread.run(Thread.java:919)
  • 通过报错提示 “not called Looper.prepare()” 可以看出提示没有调用Looper.prepare(),至于为什么我们还得看下源码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public Handler(Callback callback, boolean async) {
        ...省略若干代码

       //通过 Looper.myLooper()获取了mLooper 对象,如果mLooper ==null则抛异常
        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;
    }

 public static @Nullable Looper myLooper() {
        //而myLooper()是通过sThreadLocal.get()获取的,那sThreadLocal又是个什么鬼?
        return sThreadLocal.get();
    }

 //可以看到sThreadLocal 是一个ThreadLocal<Looper>对象,那ThreadLocal值从哪赋值的?
 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//sThreadLocal 的值就是在这个方法里赋值的
 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));
    }
  • 通过以上的源码注释,完全明白了报错的日志的意思,报错日志提示我们没有调用Looper.prepare()方法,而Looper.prepare()方法就是sThreadLocal赋值的位置。

那子线程怎么创建Handler呢?只需在new Handler()之前调用下Looper.prepare()即可。

  • 2. 为什么主线程可以直接new Handler?
  • 子线程直接new Handler会报错,主线程为什么就不会报错呢?主线程我也没有调用Looper.prepare()啊?那么我们还得看下源码了。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    //我们看下ActivityMain的入口main方法,调用了 Looper.prepareMainLooper();
    public static void main(String[] args) {
       ...
        Looper.prepareMainLooper();
        ...
    }

  //看到这一下就明白了,原来主线程在启动的时候默认就调用了prepareMainLooper(),而在这个方法中调用了prepare()。  
 //提前将sThreadLocal 进行赋值了。
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  • 3.Handler为什么会内存泄露?
  • 首先普及下什么叫内存泄露,当一个对象不再使用本该被回收时,但另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这种情况下就产生了内存泄漏。
  • 我们举一个Handler内存泄露的场景。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HandlerActivity extends AppCompatActivity {
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
}
  • 当以上代码写完后编译器立马会报黄并提示 “this handler should be static or leaks might occur...Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”

大致意思就说 “由于这个处理程序被声明为一个内部类,它可以防止外部类被垃圾回收。如果处理程序正在对主线程以外的线程使用Looper或MessageQueue,则不存在问题。如果处理程序正在使用主线程的Looper或MessageQueue,则需要修复处理程序声明,如下所示:将处理程序声明为静态类;并且通过WeakReference引用外部类”。

  • 说了这么一大堆,简单意思就是说以上这种写法,默认会引用HandlerActivity,当HandlerActivity被finish的时候,可能Handler还在执行不能会回收,同时由于Handler隐式引用了HandlerActivity,导致了HandlerActivity也不能被回收,所以内存泄露了。

我们来写一种正确的写法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HandlerActivity extends AppCompatActivity {
      MyHandler handler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
    private static class MyHandler extends Handler{
        private WeakReference<HandlerActivity> activityWeakReference;

        public MyHandler(HandlerActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}
  • 以上写法使用了静态内部类+弱引用的方式,其实如果在handleMessage()方法中无需访问HandlerActivity 的成员则无需使用弱引用,只需静态内部类即可,弱引用只是方便调用HandlerActivity 内部成员。
  • 非静态内部类和非静态匿名内部类中确实都持有外部类的引用, 静态内部类中未持有外部类的引用,不影响后续的回收,因此没有内存泄露。

4. 补充个小知识点,啥是隐式引用?

  • 其实我们写的非静态内部类和非静态匿名内部类,在编译器编译过程中,隐式帮我们传入了this这个参数,这也是为什么,我们平时在方法中能使用this这个关键字的原因,了解了隐式引用,那么为什么它会是导致内存泄漏? 这里又得说明一下,虚拟机的垃圾回收策略。
  • 垃圾回收机制:Java采用根搜索算法,当GC Roots不可达时,并且对象finalize没有自救的情况下,才会回收。也就是说GC会收集那些不是GC roots且没有被GC roots引用的对象,就像下边这个图一样。
  • 上图中的对象之间的连线就是这些对象之间的引用,垃圾回收的判定条件就在这些连线上,要预防非静态内部类的泄漏问题,就得管理好对象间的引用关系。
  • 去除隐式引用(通过静态内部类来去除隐式引用) 手动管理对象引用(修改静态内部类的构造方式,手动引入其外部类引用) 当内存不可用时,不执行不可控代码(Android可以结合智能指针,WeakReference包裹外部类实例)是解决内存泄露比较好的方式。

注意 : 不是所有内部类都建议使用静态内部类,只有在该内部类中的生命周期不可控的情况下,建议采用静态内部类。其他情况还是可以使用非静态内部类的。

视频:Android程序员备战2022FrameWork必问:handler原理

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Handler 消息机制原来解析
分为同步消息、异步消息、屏障消息。但是异步消息和屏障消息的相关API都是隐藏的,需要通过反射才能使用。
李林LiLin
2020/12/21
1K0
Handler都没搞懂,拿什么去跳槽啊?!
做 Android 开发肯定离不开跟 Handler 打交道,它通常被我们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中消息机制的重要一员也确实给我们的开发带来了极大的便利。
程序亦非猿
2019/08/16
7220
Handler都没搞懂,拿什么去跳槽啊?!
深入探讨Android异步精髓Handler
众所周知,Android的UI是在其主线程中进行刷新的,所以Google建议开发人员切勿在主线程中进行耗时的操作否则很容易导致应用程序无响应(ANR)。鉴于此几乎接近硬性的要求,我们常把耗时的操作(比如网络请求)置于子线程中进行;但是子线程不能直接访问UI。
开发者技术前线
2020/11/23
6160
深入探讨Android异步精髓Handler
Android程序员详解:Handler机制
Handler在我们日常开发中会经常用到,它主要用于处理异步消息,当发出一个消息之后,首先进入到一个消息队列,发送消息的函数即可返回,而另外一个部分在消息队列中逐一取出,然后对消息进行处理。
Android架构
2019/07/08
7240
Android程序员详解:Handler机制
Android Handler机制4之Looper与Handler简介
要理解Handler的消息机制,就不得不说Handler/Looper/Message/MessageQueue/Message这四4个类,下面我们先大概了解下这几个类
隔壁老李头
2018/08/30
9200
Android Handler机制4之Looper与Handler简介
Handler与Looper方法源码解析
在线程的run方法中调用Looper的prepare()方法进行准备工作,准备之后就可以通过Looper.myLooper获取到当前的线程的Looper了。使用然后调用loop方法进入循环处理。使用的时候非常简单。接下来分析Looper的实现。
Android架构
2019/06/18
7640
Handler与Looper方法源码解析
「细品源码」 Android 系统的血液:Handler
作为 Android 开发者,相信对于 Handler 的使用早已烂熟于心。Handler 对于 Android 非常重要,可以说,没有它,Android App 就是一堆“破铜烂铁”,它就像 Android 的血液,穿梭在 App 的各个角落,输送养分。
开发的猫
2020/06/19
1K0
「细品源码」 Android 系统的血液:Handler
【Android】Handler 机制 ( Handler | Message | Looper | MessageQueue )
① 子线程更新 UI : 在子线程中更新 UI , 就是在子线程中将刷新 UI 的任务分配给了主线程 ; ( 子线程刷新 UI 会崩溃 )
韩曙亮
2023/03/27
1.5K0
Android 源码分析 —— Handler、Looper 和 MessageQueue
在分析 Toast 源码的过程中我们涉及到了 Handler,这个在 Android 开发里经常用到的类——线程切换、顺序执行、延时执行等等逻辑里往往少不了它的身影,跟它一起搭配使用的通常是 Runnable 和 Message,还有它身后的好基友 Looper 与 MessageQueue。Runnable 相信大家都很熟悉了,本文的主角就是标题里的三剑客——Handler、Looper 和 MessageQueue,当然少不了说到 Message。
mzlogin
2020/04/16
6820
Android 源码分析 —— Handler、Looper 和 MessageQueue
面试必备:ThreadLocal+Looper+Handler
文章目录 一、Handler使用与概述 1.1使用步骤 1.2Handler的使用背景 二、Android消息机制分析 2.1 ThreadLocal 2.2 messageQueue 2.3 Looper 2.4 Handler 三、主线程的消息机制 Handler是消息机制的上层接口,开发中基本只用和Handler交互即可。Handler可以将一个任务切换到Handler指定的线程中执行。如在用Handler在子线程更新UI。 Android消息机制主要就是Handler的运行机制。Handler的运行
胡飞洋
2020/07/23
7400
面试问关于Handler的这些问题你知道吗?
Q :在线程中可以直接调用 Handler 无参的构造方法吗?在主线程和子线程中有没有区别? A:在主线程中可以;在子线程中会抛出RuntimeException, 需要先调用 Looper.prepare()。主线程在启动的时候已经在调用过Looper.prepare()。
103style
2022/12/19
3050
异步线程大师Handler(源码+图+demo+常见问题)
Handler 机制 源码+图+常见问题+Demo 详细记录(本文内容略长,但内容较为详细,推荐Android开发者可深入观看.如有问题,欢迎指正)
Anymarvel
2019/03/19
5300
异步线程大师Handler(源码+图+demo+常见问题)
[Android进阶】Handler机制原理解析
Handler是Android中提供的一种异步回调机制,也可以理解为线程间的消息机制。为了避免ANR,我们通常会把一些耗时操作(比如:网络请求、I/O操作、复杂计算等)放到子线程中去执行,而当子线程需要修改UI时则子线程需要通知主线程去完成修改UI的操作,则此时就需要我们使用Handler机制来完成子线程与主线程之间的通信。
程序员小何SS
2021/12/06
1.4K0
Android :安卓学习笔记之 Handler机制 的简单理解和使用
有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图:
233333
2024/06/25
1.7K0
Android :安卓学习笔记之 Handler机制 的简单理解和使用
Handler源码和9个常见问题的解答,这些你都掌握了吗?
Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间,如果你想在Android中实现一个队列的功能,不妨第一时间考虑一下它。
Android技术干货分享
2020/11/17
1.4K0
Handler源码和9个常见问题的解答,这些你都掌握了吗?
Android Handler 消息处理机制
日常开发中,一般不会在子线程中直接进行 UI 操作,大部分采取的办法是创建 Message 对象,然后借助 Handler 发送出去,再在 Handler 的 handlerMessage() 方法中获取 Message 对象,进行一系列的 UI 操作。Handler 负责发送 Message, 又负责处理 Message, 其中经历了什么 ,需要从源码中一探究竟。
用户3596197
2018/10/15
5110
源码分析——Android Handler是如何实现线程间通信的
Handler 作为 Android 消息通信的基础,它的使用是每一个开发者都必须掌握的。开发者从一开始就被告知必须在主线程中进行UI操作。但 Handler 是如何实现线程间通信的呢?本文将从源码中分析 Handler 的消息通信机制。
阳仔
2019/07/31
8520
源码分析——Android Handler是如何实现线程间通信的
想实现安卓队列功能?Handler内功心法,你值得拥有!——Handler源码和常见问题的解答
Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间,如果你想在Android中实现一个队列的功能,不妨第一时间考虑一下它。本文分为三部分:
Android技术干货分享
2020/12/28
2K0
想实现安卓队列功能?Handler内功心法,你值得拥有!——Handler源码和常见问题的解答
Android全面解析之由浅及深Handler消息机制
关于Handler的博客可谓是俯拾皆是,而这也是一个老生常谈的话题,可见的他非常基础,也非常重要。但很多的博客,却很少有从入门开始介绍,这在我一开始学习的时候就直接给我讲Looper讲阻塞,非常难以理解。同时,也很少有系统地讲解关于Handler的一切,知识比较零散。我希望写一篇从入门到深入,系统地全面地讲解Handler的文章,帮助大家认识Handler。
huofo
2022/03/18
8510
Android全面解析之由浅及深Handler消息机制
Handler的初级、中级、高级问法,你都掌握了吗?
Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间,如果你想在Android中实现一个队列的功能,不妨第一时间考虑一下它。本文分为三部分:
没关系再继续努力
2021/12/30
1.2K0
推荐阅读
相关推荐
Handler 消息机制原来解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验