`
Z_萧晓
  • 浏览: 11265 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

掌握关于Handler的这些基本问题,让你的面试事半功倍!

阅读更多

前言

Handler是个老生常谈的问题,我相信几乎所有的Android开发者都会使用Handler,那关于Handler还有什么好讲的吗?Handler如果仅仅是使用的话,确实没什么好讲的,但是Handler却是一个几乎所有面试官都会问的问题,不同的要求问的深度也不一样,今天我就带大家学习一下关于Handler你所必须要掌握的知识。

 

Handler消息机制

首先有四个对象我们必须要了解一下Handler、Looper、ThreadLocal还有MessageQueue。

Handler

首先我们要明白Handler消息机制是用来干嘛的?Handler是把其他线程切换到Handler所在的线程,注意,这里不是UI线程。虽然我们的大多数使用Handler的场景,都是我们在子线程做了一下耗时的操作(IO或者数据库),当子线程执行完以后我们可能需要更新UI,这个时候我们用Handler来处理(sendMessage()或者post())就把子线程切换到了UI线程了。假如说,我们现在有这么一个需求,线程A发个信息给线程B(线程A、线程B都不是主线程),这个时候我们用Handler依然可以做,只需要在线程B中创建好Handler就可以了(关于如何在子线程创建Handler我会在下面详细说明)。所以,Handler并不是把其他线程切换到主线程(UI线程),而是切换到它所在的线程,这一点一定要弄清楚。

Looper

Handler消息机制里面最核心的类,消息轮询。Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper,然后调用Looper.loop()方法把MessageQueue里面的message轮询出来"交"给Handler来处理,至于如何“交”给Handler的,下面我会通过源码带大家分析。

ThreadLocal

ThreadLocal是Looper内部的一个,它虽然前面有个“Thread”,但其实它并不是线程,它相当于一个线程内部的存储类。刚才在讲Looper的时候我们说到,“Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper”,那如何获得当前线程的Looper呢?正是通过ThreadLocal,它可以在不同线程之间互不干扰地存储数据。ThreadLocal里面有一个内部静态类对象ThreadLocalMap,通过其set()和get()方法对数据对象进行保存和读取。

MessageQueue

MessageQueue——消息队列,虽然它叫消息队列,但实际上的结构并不是一个队列,而是一种单链表的数据结构,只是使用列队的形式对数据进场做添加或者移除。MessageQueue是在Looper被创建的时候由Looper生成的。同一线程下,Handler发送的所有message都会被加入到MessageQueue里面,当Message被loop()以后,就会从消息队列中移除。Message我没有单独拿出来,因为确实比较简单,就是消息本身,它可以携带两个int型参数,如果要传比较复杂的参数就用obj属性,what属性用来区分是哪个Handler发送的信息。

小结一下

Handler是把其他线程切换到它所在的线程,使用Handler发消息之前要先创建Looper对象,创建和读取Looper都需要使用ThreadLocal,它可以在不同线程之间互不干扰地保存数据。在创建Looper对象的同时也把MessageQueue创建好了,Handler所发的message会被添加到MessageQueue对象里面,Looper.loop()方法以后会把MessageQueue上的Message轮询出来。连接这几个对象的还有一条很重要的线,那就是线程,记住,以上的过程都要保证Handler、Looper、MessageQueue在同一线程,这样,才能保证Handler的消息机制被正确使用。

子线程创建Handler

注意:我们这里是为了让大家更好地学习Handler,所以要在子线程创建Handler,现实使用场景,很少会在子线程创建Handler

 

在主线程(UI线程)创建Handler,我相信所有人都会

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainHandler = new Handler();
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.e("qige_test", "thread_name=" + Thread.currentThread().getName());
            }
        });
    }

我们先按照主线程的方式在子线程创建一个Handler试一下,看看会有什么样的结果

 new Thread(){
            @Override
            public void run() {
                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

            }
        }.start();

 

没错,如图所示还没有创建Looper,那么如何创建Looper呢?图中有句话已经给出了答案——Looper.prepare(),同时为了让我们发送的消息被轮询到,还必须要调用Looper.loop(); 所以在完整的在子线程创建Handler的代码如下:

 new Thread(){
            @Override
            public void run() {
                //创建Looper,同时创建MessageQueue
                Looper.prepare();

                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

                //轮询
                Looper.loop();

            }
        }.start();

这样就可以在子线程里面创建Handler了,看到这里,你可能会问,为什么我在主线程创建Handler的时候,没有调用Looper.prepare()和Looper.loop()?这个问题我们先放一下,我们先从源码角度把Handler消息机制分析一下。

从源码角度分析

先看一下创建Looper和MessageQueue的方法Looper.prepare()

public static void prepare() {
        prepare(true);
    }

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

这里就出现了我刚才说的ThreadLocal对象。Android规定每个线程有且仅有一个Looper,所以为了防止不同的线程之间的Looper相互影响,使用ThreadLocal对象来存储Looper。

再看一眼new Looper()的源码

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

MessageQueue也出来了,创建Looper的同时创建MessageQueue。

下面我们看Handler.post()或者是Handler.sendMessage()他们本质是一样的,把消息加入到消息队列当中

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

post()或者sendMessagexxx()最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)方法,我们直接看这个方法的源码

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //关键代码部分
        return enqueueMessage(queue, msg, uptimeMillis);
    }

都很简单,最后一步enqueueMessage(queue, msg, uptimeMillis);是把消息加入队列,咱们再点开看一下

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //把当前的Handler赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

关键要看的地方是 msg.target = this刚才讲Message的时候没有提,Message的target属性就是一个Handler对象,这里直接把当前的Handler对象赋值过去了。最后一行:queue.enqueueMessage(msg, uptimeMillis)是把message加入到消息队列里面,具体的实现我抓不到了,知道这句话是把message加入到MessageQueue里面就行。
下面分析* Looper.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.");
        }
        **标注1**
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
       ........................
       ......................
       **标注2**
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ....................................
            ...................................
            try {
               **标注3**
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           ........................................
           ........................................
             **标注4**
            msg.recycleUnchecked();
        }
    }

看源码的时候注意看一下标注,一个个地来讲;

  • 标注1:保证了Handler、Looper和MessageQueue在同一线程
  • 标注2:没有看错,就是一个无线死循环,Looper会不停地对MessageQueue进行轮询。这时候,你可能会问,这种无限死循环会不会很浪费资源?其实并不会,因为当没有message被添加到队列的时候,程序会进入阻塞状态,对资源的消耗是很小的。
  • 标注3:还记得刚才讲地msg.target吧,这里 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看Handler的dispatchMessage(msg)方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里面msg.callback是个Runnable对象,也就是当我们使用handler.post(Runnable r)的方法的话,这时候就直接去调用Runnable对象里面的东西了,如果使用的是handler.sendMessagexxx(),就是去调用我们重写的handleMessage(msg)方法了。
如果调用post()方法发送消息

mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        //这里会被调用
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

如果调用sendMessagexxx()

 mainHandler.sendEmptyMessage(0);
        mainHandler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                //这里会被调用
                Log.e("qige_test","what="+msg.what);
            }
        };

我们再回到上一级源码

  • 标注4, msg.recycleUnchecked();其实这里就是msg完成了它的使命,被释放了以后又放回缓存池里面去了。

以上基本就是一个完整的Handler消息机制的过程,我再带大家好好回顾一下:

1.Looper.prepare();//这一步创建了Looper和MessageQueue
2.handler.sendMessagexxxx(); // 把message添加到MessageQueue上
3.Looper.loop();//轮询消息
4.handler.dispatchMessage(msg);//处理消息

关于在主线程创建Handler的疑问

好了,到了该解决历史遗留问题的时候了,为什么我们在主线程创建handler不需要调用Looper.prepare()和Looper.loop()呢?
这是因为,主线程已经给我们创建好了,在哪里创建好的呢?
在Java里面,有一个程序的主入口,就是静态main方法

public class Test {
    //程序入口
     public static void main(String... args){

     }

在Android里面呢,同样有这么一个main方法入口,它在ActivityThread类中。在我们App启动过程中,会调用到ActivityTread的main()方法(由于篇幅问题,该过程没有办法详细讲,后期会推文章单独说一下),下面我们直接看ActivityTread的main()方法的源码
为了方便理解,还是只看关键部分代码

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

        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        //消息轮询
        Looper.loop();

    }

看到这里,我想你应该明白了为啥在主线程里创建Handler不需要调用Looper.prepare()和Looper.loop()方法了吧,因为在App启动的时候,ActivityThread已经给我们创建好了。不过,需要注意的是,我刚才在讲Looper.loop()源码的时候说过这里面会有一个无限死循环,那么程序运行到这里岂不是要永远卡在这了呢,那我们的Activity、Service都是咋执行的呢? 看关键的源码这一句thread.attach(false),注释上其实解释的很清楚,通过Binder创建了新的线程,在这个新的线程里面运行了Activity、Service等组件,而之所以Android为什么采用这种方式,让我们留在以后再说,你只需要知道,Looper.loop()不会造成卡顿,主线程已经给我们创建好了Looper和MessageQueue对象就可以了。

 

为什么写这篇文章

开篇我就说过,Handler几乎所有人都会用,但仅仅会用是不够,要知其然更知其所以然。很多面试官愿意问Handler相关的问题,好好阅读这篇文章,它会让你在面试的时候事半功倍。

有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后文末我为大家准备了一套精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。(以下是一小部分,获取更多其他精讲进阶架构视频资料可以加我wx:X1524478394 免费获取)

一下是今天给大家分享的一些独家干

①Android开发核心知识点笔记

 

②面试精品集锦汇总

 

③全套体系化高级架构视频

 

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

 

上述【高清技术脑图】以及【配套的架构技术PDF】可以 加我wx:X1524478394 免费获取

分享到:
评论

相关推荐

    Handler消息处理机制+面试说.md

    ### Handler消息处理机制详解 #### 一、原理 在Android开发中,`Handler`...通过这样的结构化介绍,不仅能够展现你对`Handler`机制的理解深度,也能让面试官更加清晰地了解到你在实际项目中的实践经验和技术能力。

    Android开发工程师面试题之handler详解。android程序员,android开发面试资料,详解

    ### Android开发工程师面试题之Handler详解 在Android开发过程中,Handler是进行线程间通信的重要机制之一,尤其在实现异步任务更新UI等场景中扮演着关键角色。本篇文章将围绕Handler的工作原理、核心组件及其实现...

    Handler的基本用法

    了解了Handler的基本用法后,开发者可以更高效地控制线程间的通信,确保UI更新的流畅性,同时避免了主线程被长时间阻塞的问题。在实际项目中,结合使用Handler、AsyncTask或其他异步处理方式,可以实现更加灵活和...

    Handler源码分析流程以及面试问题解答

    Handler是Android系统中用于线程间通信的重要...以上是对Handler源码分析及面试问题解答的概述,深入理解这些知识点对于Android开发人员来说至关重要,有助于解决实际开发中遇到的问题,也能提高系统优化和调试的能力。

    Handler的基本使用

    本文将详细介绍`Handler`的基本使用及其在更新UI中的应用。 首先,我们需要理解Android的线程模型。Android应用的主要执行流程发生在主线程,也称为UI线程,负责处理用户交互和更新UI。而其他耗时的操作(如网络...

    安卓关于handler的使用

    在Android开发中,`Handler`、`Looper`和`Message`是实现线程间通信的重要...在提供的文件`thread_test1`中,可能包含了具体的`Handler`使用示例代码,你可以参考这些代码进一步理解`Handler`的工作原理和使用方法。

    10道Java高级必备的Netty面试题!.zip

    这份PDF文档将帮助Java开发者尤其是面试者深入理解Netty框架,提高他们的面试准备水平,以便在面试中能准确、自信地回答这些问题,展示自己对Java高级特性和Netty框架的掌握程度。通过学习和理解这些知识点,开发者...

    Android 面试基本问题

    在Android面试中,掌握基本知识点和扩展知识点是至关重要的,这不仅体现了...这些知识点是Android开发者面试中的常见问题,掌握它们能够帮助求职者更好地展示自己的专业能力,同时也有助于在实际工作中解决各种问题。

    Handler使用

    在Android开发中,`Handler`是一个至关重要的组件,主要用于解决线程间的通信问题,尤其是在UI线程和其他工作线程之间。本篇文章将深入探讨`Handler`的使用方法,包括其基本概念、工作原理以及如何避免内存泄露和...

    Android面试Handler.docx

    在Android开发中,Handler是一个非常重要的概念,它主要用于解决主线程(UI线程)与子线程之间的消息传递问题。通过合理运用Handler,可以有效地避免UI线程被长时间占用,进而提高应用的响应速度和用户体验。 #### 二...

    关于handler 的实例

    总的来说,这个关于Handler的实例是Android开发中的一个基础但关键的学习点,通过它,开发者可以掌握如何有效地管理线程,保证UI的流畅更新,以及如何进行线程间的通信。对于Android应用的性能优化和用户体验提升...

    Fragment与Activity使用Handler进行交互

    首先,了解Handler的基本概念。Handler是Android中的一个消息处理类,它主要用来处理运行在主线程中的Message对象。通过创建一个Handler实例并与Looper(消息循环)关联,我们可以发送和处理Message,从而实现在不同...

    安卓handler的使用

    在Android应用开发中,Handler是一种重要的线程通信机制,它与Looper、Message紧密配合,用于在不同的线程间传递消息,解决多线程同步问题。本篇将详细讲解Handler的使用,包括如何创建Handler对象、如何发送和处理...

    android 中Handler 的几种写法

    `AsyncTask`内部使用了`Handler`来更新UI,我们可以通过重写`onProgressUpdate()`方法来处理这些消息。此外,也可以在`doInBackground()`方法中使用`publishProgress()`手动发送进度更新消息。 5. **Looper的使用*...

    handler的简单示例

    在Android开发中,Handler是一种非常重要的机制,它用于在主线程中处理来自其他线程的消息,从而实现线程间通信,尤其是对UI...通过实践和学习更多关于Handler的知识,开发者可以更好地掌握Android应用的异步编程技巧。

    Handler 推荐用法 demo

    msg.obj = "Hello, Handler!"; // 设置携带的数据 mHandler.sendMessage(msg); ``` 3. **处理Message**: 当`Handler`接收到消息后,`handleMessage()`方法会被调用,开发者在此方法内执行相应的操作,如更新...

    handler用法

    本篇文章将详细讲解Handler的基本概念、工作原理以及如何在实际项目中应用。 首先,Handler的核心作用是在不同的线程间传递消息(Message)并执行相应的回调方法。在Android中,大部分UI操作必须在主线程(也称为UI...

    handler全面分析

    在面试中,面试官可能会询问关于Handler的内存泄漏问题。由于Handler实例通常持有对Activity的引用,如果不正确地管理,可能导致Activity无法被垃圾回收,从而引发内存泄漏。为避免这种情况,我们可以在Activity的`...

    初中级Android开发社招面试之Handler.zip

    在Android应用开发中,Handler是实现线程间通信的关键组件,尤其对于初、中级开发者来说,理解和掌握Handler机制是面试中的必备技能。本篇将详细阐述Handler的核心概念、工作原理以及实际应用,帮助你应对社招面试。...

    Handler实例

    `Handler`是Android提供的一个关键工具,用于解决多线程环境下的消息传递和处理问题。本篇文章将深入探讨`Handler`实例及其在Android线程通信中的应用。 首先,线程间通信在Android中通常涉及到主线程(UI线程)与...

Global site tag (gtag.js) - Google Analytics