关于Handler的各种梗QA

作者: 波澜步惊 | 来源:发表于2019-06-03 15:01 被阅读70次

    前言

    矮油Handler,老铁你又来了。

    正文

    如题:

    做安卓开发的,无人不知,但是很少有人全知。

    通过各种途径专门收集了一些关于handler的梗,没事翻翻看,温故而知新。


    QA_1 多个线程是如何通讯的?

    通讯?其实就是数据交互。在android中,线程之间的数据交互其实就是通过 内存共享 来实现的。
    所谓内存共享,就是有一片内存空间存了一堆数据,线程A可以访问,线程B也可以访问。这就是内存共享。Handler一般用于开一个子线程,然后handler.send/postXXX来向主线程发送消息,这过程中,子线程和主线程其实就是在访问同一片内存空间的数据。

    QA_2 Android编程中肯定是有多线程,但是好像很少看到wait/notify,为何?

    因为谷歌大佬已经linux层,对wait/notify这种操作做了封装,我们使用了Handler,就自动处理了多线程同步的问题,让app开发变得更简单.
    扩展知识:面向对象编程七大原则之一:最少知道原则一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。),开发者只需要知道Handler可以解决线程通讯的问题,而不需要担心会出现其他问题。

    QA_3 一张图说明handler机制:

    handler机制图说.jpg

    QA_4 用文字简述Handler的工作流程:

    1. 通常,我们使用Handler,都会事先获取一个Message,获取Message的方式有两种,new Message() 或者 Message.obtain,通过源码我们可以观察到,前者,就是纯粹地创建一个Message对象,后者则会从message单链表队列中去获取一个(获取不到才会返回一个new Message()).

    2.Handler发送消息的方式是 使用sendMessage系列方法,也可以通过postXXX方法来传入一个Runnale,但是他们最终都是将一个message,通过enqueueMessage()message放到了MessageQueue

    3.消息入列之后,关键代码就到了MessageQueue中了,MessageQueue只是一个队列,它提供 入列方法enqueueQueue,出列方法next. 这些方法给谁用呢? 入列,是Handler控制的,那么出列则是Looper调用。

    4. Looperloop方法,是闭合"传送带"的动力开关,开启一个for死循环,循环中调用了 MessageQueuenext方法,取得一个Message.如果取不到(当前没有符合条件的Message,分情况,1,存在一个或者多个message,但是都有delay时间,不是立即执行的Message,它会取出延迟时间最短的那个,然后阻塞延迟的时间长度2 队列中不存在任何Message,会一直阻塞,直到有Message),就会阻塞,阻塞的位置在MessageQueue类的nativePollOnce方法,它是一个native方法,注意这里还会给一个全局变量 mBlocked置为true

    5.当Looperloop-for循环阻塞的时候,如果此时有新的消息入列,并且这个消息是 需要立即执行的,那么 给全局变量needAweak置为mBlockedtrue值。如果这个消息不是立即执行,那就按照延迟时间插入到队列里面,越先执行的排在最前面,此时并不会将needAweak变为true。 最后,根据needAewak的值,决定是否取消阻塞,唤醒线程。当前这些动作都是在主线程中执行,所以,主线程的MessageQueue next方法阻塞,则会释放CPU,当再次被唤醒,就会重新获得CPU时间片。

    6. Looperloop方法是MessageQueue开始滚动的触发开关。这个loop方法其实是由,当前线程去调用的。当app启动,ActivityThreadmain方法就会执行,在main中,就调用了Looper.loop方法。仔细看loop方法的过程,就会发现,它先获取了Looper looper = myLooper();,而这个myLooper 则是从ThreadLocal中去获得Looper对象。

    7.ThreadLocaljava中实现了线程隔离,在handler机制中,它为每一个Thread,提供了唯一一个Looper对象的存储位置。也就是说,一个Thread中,只有一个Looper,只有一个MessageQueue,可以有多个Message存在于MessageQueue,并且处理过的Message将会进入到sPool作为复用项。 当多个线程,向这个线程发送Message的时候,考虑到线程安全,源码中使用了同步关键字 sysnchronized写了一个同步代码块。

    QA_5 给一个Message设置消息处理回调有几种方式?

    一共3种方式,Handler源码中,对消息处理的回调设计很巧妙,类似线程的run方法的处理(可以传一个Runnable,也可以重写Threadrun方法),Handler设计了三重判定,首先看Message本身的callback成员是否为空,不为空则由Message自己处理;如果Messagecallback为空,则看Handlercallback成员是否为空,不为空,则由这个callback去处理;如果还是空,则由Handler本身的handlerMessage方法去处理。这个,说不上是什么设计模式,好像也没有哪种设计模式直接匹配这种做法。但是这种做法有点像ClassLoader的双亲委托机制,能让别人干的绝对不自己干,应该是某种编程思想的具体体现吧。

    QA_6 Handler 源码中有没有用到什么设计模式?

    最明显的设计模式就是 享元模式, 通俗一点说,就是内存共享,对象复用。 就好比String类,当我们创建一个字符串abcd之后,随后再创建一个abcd,这个时候,底层并不会去new一个String对象,而是会直接将这个引用指向之前创建好的abcd,实现复用,节约内存开销。handlersPool,就是Message的池子,用完的,放在这里,下次new的时候,优先从这里获取,池子里没有,再new.
    另外,ThreadLocal 实现了线程的内存隔离,让每一个线程拥有自己的泛型对象,仅此一份。非要说一个模式的名字,那就是线程单例,据说在java后台经常用到。

    QA_7 我们都知道,在Looper.loop()之后,会开始一个无限循环,从MessageQueue中获取Message,那这个循环如何停止的?

    Loop退出循环提供了一个退出方法,quit() / quiteSelfty(),其实都是调用的messageQueue消息队列的quit(bool )方法,停止了Looper对队列的循环next。这里其实有区别,不安全退出,和安全退出。前者会 循环队列中的每一个消息,执行recycleUnchecked回收操作,然后把消息头置为空。后者,会判断消息的执行时间when和当前时间的前后,如果是when大于当前时间,则直接执行不安全退出的过程,可是如果消息的执行时间小于当前时间,那就把这些小于当前时间的消息忽略,直接对大于当前时间的消息进行recycleUnChecked回收.
    并且,提一个细节:当Looper.quit的时候,会将一个标志位:mQuitting 置为true,然后 MessageQueuenext方法中,会判断mQuitting,如果true,就返回null,而 Looperloop循环中,发现MessageQueue.nextnull时,会return退出循环。

    QA_8 如何解决handler引起的内存泄漏?

    Handler发送消息是支持延迟时间的,所以我们不能确定该消息的执行的时候,Activity一定还活着。
    所以当我们在Activity中去new Handler,很有可能导致Activity不被回收。解决类似问题较为简单的有两种方案,第一:使用弱引用的方式(可以用静态内部类+弱引用,也可以另外建一个工具类+弱引用,总之不要让Handler持有Activity的强引用),第二:既然Activity命短,那就不要在这里建了,直接到Application里面去建Handler,然后提供给外部。

    相关文章

      网友评论

        本文标题:关于Handler的各种梗QA

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