美文网首页
Handler相关看这篇就够了

Handler相关看这篇就够了

作者: 俗人浮生 | 来源:发表于2019-03-11 21:15 被阅读0次

说起Handler,很容易就想起了Handler、Looper、Message、MessageQueue这4个东东,下面,我们通过几个问题来加深对Handler的了解和学习:

首先,我们先做一下梳理:

1、一个Thread里可以有多个Handler

这个没什么好说的,你想在一个Thread里面创建多少个Handler都行,只是有没有这个必要而已。

2、一个Thread只能对应一个Looper

我们都知道使用Looper的第一步需要调用prepare(),下面我们直接来看一下这个方法的源码:

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

   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));
    }

很明显,prepare()调用了prepare(boolean quitAllowed)方法,其中的sThreadLocal是一个ThreadLocal<Looper>对象,也就是说,使用了ThreadLocal来为每一个线程创建一个Looper副本,而且是有且只有一个,从以上代码也可以直观的说明这一点,如果已经有Looper了,再调用prepare()是会报运行时错误的。(ThreadLocal相关请参考这个

3、一个Looper只能管理着一个MessageQueue

这个也没什么好说的,源码中有且只有一个,而Handler中的MessageQueue用的是Looper中的MessageQueue

4、由以上就可以得出,一个Thread中的多个Handler共享了一个Looper和一个MessageQueue

比如在同一Thread有Handler a和Handler b,那么调用a.sendMessage(msg1)和b.sendMessage(msg2)后,msg1和msg2都会进入到同一个MessageQueue中,然后由同一个Looper进行分发给自己的Handler进行消费。

5、为什么在主线程中新建Handler不用调用Looper.prepare()和Looper.loop()方法呢?

是的,如果我们在主线程使用Handler的话,那么我们直接Handler handler=new Handler();就可以用了,但是如果你在子线程这样的话是不行的,因为Looper都没创建,必须先调用Looper.prepare()为子线程创建Looper对象,并且之后调用Looper.loop()进行消息队列的循环。
那么为什么在主线程可以不用呢?其实最终还是调用了,只是不用我们自己显示编码而已,我们看一下主线程的入口类ActivityThread的源码,如下:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//在这里!!!

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

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

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();//在这里!!!

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

 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

源码已经很清晰的说明这一点了(文中中文注释),这里就不做过多的说明了。

6、HandlerThread又是什么呢?与Handler有什么关系呢?

首先,HandlerThread继承于Thread,也就是说,它是一个线程,那么,它肯定有run方法,我们直接看一下其源码:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();//在这里!!!
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();//在这里!!!
        mTid = -1;
    }

注意上面源码中的中文注释,也就是说,HandlerThread帮我们完成了Looper的创建及相关实现什么的,这样我们就可以在子线程中轻松愉快的使用Handler了,使用起来的代码大概是这样子的:

    HandlerThread handlerThread=new HandlerThread("myThread");
    handlerThread.start();
    Handler handler=new Handler(handlerThread.getLooper());

使用是很简单,但我们为什么要用HandlerThread呢?
首先,如果是长时间多任务的话,肯定是不能放在主线程进行的,因为这样会阻塞主线程,导致触摸啊输入啊等一些动作的响应,甚至会出现ANR。
于是,对于此类任务,我们肯定是要在子线程中去完成的,而HandlerThread让我们可以像在主线程中使用Handler那样,在子线程中方便快捷的使用Handler,所以,我们为何不用呢?
当然,请注意一点,在使用完HandlerThread后,请调用handlerThread.quit()来释放Looper。

7、为什么在主线程中调用了Looper.loop()这个死循环不会导致ANR呢?

这个经常是作为一个面试题来着的,如果你平常没思考过的话,很容易会被问懵圈的,但当你想通了之后,你会发现,这个问题其实并不是个问题啊!
首先,我们上面第5点已经说明了,我们在主线程的入口类ActivityThread调用了Looper.loop()进行了消息循环,而如果一旦退出消息循环的话,你的应用也就退出了。
而Android 是由事件驱动的,Looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
另一方面,主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
当然,如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
因此,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。(参考这里

相关文章

  • Handler相关看这篇就够了

    说起Handler,很容易就想起了Handler、Looper、Message、MessageQueue这4个东东...

  • ThreadLocal源码理解

    为啥要写这篇文章 起初我看Handler相关源码,看到Looper里面有个ThreadLocal,如下,而这个Th...

  • iOS绘图系统(二) Core Animation (读书笔记)

    想快速掌握Core Animation,看这篇文章就足够了,通过这篇文章你就可以对Core Animation相关...

  • 2017-08-10读书笔记(Webpack学习笔记6)

    今天继续学习Webpack技术。 文章是这篇 入门Webpack,看这篇就够了 css预处理器相关组件常用的是以下...

  • Handler Looper MessageQueue之间的协作

    上篇文章分析了Handler消息机制,这篇文章就分析Looper MessageQueue Handler 之间的...

  • 前端,看这篇就够了

    转载说明 结合个人经历总结的前端入门方法,总结从零基础到具备前端基本技能的道路、学习方法、资料。由于能力有限,不能...

  • JVM看这篇就够了

    概念 虚拟机:指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实...

  • 板材?看这篇就够了

    写了不少关于木作方面的文章,总有人对板材辨别感到头晕脑涨。我向别人介绍的时候通常要费好大劲,才能把所讲的板材知识讲...

  • JavaGC,看这篇就够了

    1. 什么是JavaGC Java GC(Garbage Collection,垃圾回收)机制,顾名思义,就是Ja...

  • 掌握SSH这篇就够了

    SSH 是每一台电脑的标准配置,Linux 就不必说了,连 windows[https://docs.micros...

网友评论

      本文标题:Handler相关看这篇就够了

      本文链接:https://www.haomeiwen.com/subject/quelpqtx.html