关于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单链表队列中去获取一个(获取不到才会返回一个newMessage()).
  • 2.Handler发送消息的方式是 使用sendMessage系列方法,也可以通过postXXX方法来传入一个Runnale,但是他们最终都是将一个message,通过enqueueMessage()把message放到了MessageQueue中
  • 3.消息入列之后,关键代码就到了MessageQueue中了,MessageQueue只是一个队列,它提供 入列方法enqueueQueue,出列方法next. 这些方法给谁用呢? 入列,是Handler控制的,那么出列则是Looper调用。
  • 4Looper的loop方法,是闭合"传送带"的动力开关,开启一个for死循环,循环中调用了 MessageQueue的next方法,取得一个Message.如果取不到(当前没有符合条件的Message,分情况,1,存在一个或者多个message,但是都有delay时间,不是立即执行的Message,它会取出延迟时间最短的那个,然后阻塞延迟的时间长度,2 队列中不存在任何Message,会一直阻塞,直到有Message),就会阻塞,阻塞的位置在MessageQueue类的nativePollOnce方法,它是一个native方法,注意这里还会给一个全局变量 mBlocked置为true。
  • 5.当Looper的loop-for循环阻塞的时候,如果此时有新的消息入列,并且这个消息是 需要立即执行的,那么 给全局变量needAweak置为mBlocked的true值。如果这个消息不是立即执行,那就按照延迟时间插入到队列里面,越先执行的排在最前面,此时并不会将needAweak变为true。最后,根据needAewak的值,决定是否取消阻塞,唤醒线程。当前这些动作都是在主线程中执行,所以,主线程的MessageQueuenext方法阻塞,则会释放CPU,当再次被唤醒,就会重新获得CPU时间片。
  • 6.Looper的loop方法是MessageQueue开始滚动的触发开关。这个loop方法其实是由,当前线程去调用的。当app启动,ActivityThread的main方法就会执行,在main中,就调用了Looper.loop方法。仔细看loop方法的过程,就会发现,它先获取了Looper looper = myLooper();,而这个myLooper 则是从ThreadLocal中去获得Looper对象。
  • 7.ThreadLocal在java中实现了线程隔离,在handler机制中,它为每一个Thread,提供了唯一一个Looper对象的存储位置。也就是说,一个Thread中,只有一个Looper,只有一个MessageQueue,可以有多个Message存在于MessageQueue,并且处理过的Message将会进入到sPool作为复用项。
    当多个线程,向这个线程发送Message的时候,考虑到线程安全,源码中使用了同步关键字 sysnchronized写了一个同步代码块。

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

一共3种方式,Handler源码中,对消息处理的回调设计很巧妙,类似线程的run方法的处理(可以传一个Runnable,也可以重写Thread的run方法),Handler设计了三重判定,首先看Message本身的callback成员是否为空,不为空则由Message自己处理;如果Message的callback为空,则看Handler的callback成员是否为空,不为空,则由这个callback去处理;如果还是空,则由Handler本身的handlerMessage方法去处理。

这个,说不上是什么设计模式,好像也没有哪种设计模式直接匹配这种做法。但是这种做法有点像ClassLoader的双亲委托机制,能让别人干的绝对不自己干,应该是某种编程思想的具体体现吧。

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

最明显的设计模式就是 享元模式, 通俗一点说,就是内存共享,对象复用。 就好比String类,当我们创建一个字符串abcd之后,随后再创建一个abcd,这个时候,底层并不会去new一个String对象,而是会直接将这个引用指向之前创建好的abcd,实现复用,节约内存开销。

handler的sPool,就是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,然后 MessageQueue的next方法中,会判断mQuitting,如果true,就返回null,而 Looper的loop循环中,发现MessageQueue.next是null时,会return退出循环。

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

Handler发送消息是支持延迟时间的,所以我们不能确定该消息的执行的时候,Activity一定还活着。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值