前言
矮油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.
Looper
的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
的值,决定是否取消阻塞,唤醒线程。当前这些动作都是在主线程中执行,所以,主线程的MessageQueue
next
方法阻塞,则会释放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
,然后提供给外部。
网友评论