Android基础之Handler

/ 0评 / 1

今天先讲讲 Handler,如果被问到“讲讲你说 Handler 的理解”,怎么把这么一大串内容讲清楚?

0. Handler 能干啥?

异步,线程间通信,

1. 多个线程是如何通讯的?

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

2. Handler, Looper, MessageQueue, Thread,这几个东西是什么关系?

来个图

Handler 是一个通信的工具,可以是线程内,也可以是多线程之间;通信可以是发送一个消息,也可以是发送一个任务;我们利用 Handler 发送消息/任务,那么谁来处理消息/任务?处理过程是怎样的?

那这就引出了 MessageQueue,我们发消息只是向队列里面加一个消息,由处理者线程去循环从队列里面取出消息,并且处理;那么这个处理者线程就是调用 Looper.loop 的线程,我们应该都遇到过 Can’t create handler inside thread that has not called Looper.prepare() 这个错误,这是因为我们 new Handler 的线程没有调用 Looper.prepare。

可不可以两个不同的线程分别调用 Looper.prepare 和 Looper.loop?这其实是个伪命题,这两个方法的调用,由 myLooper 联系起来,而 myLooper 是利用 ThreadLocal 来存储 Looper 实例的,所以,能正常运作的 Looper,一定是在同一个线程调用的这两个方法。也就是说,最终处理消息的线程,就是调用 Looper.prepare 和 Looper.loop 的线程。

所以,Looper, MessageQueue, Thread 是一一对应的关系,而这一套东西,可以对应多个 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写了一个同步代码块。

3. Looper.loop 为什么不会阻塞住主线程?

主线程被阻塞的定义是什么?其实就是主线程的 MessageQueue 中,某个消息的处理时间过长,导致后面的消息不能及时处理。

那 Looper.loop 会不会阻塞主线程?看在哪里调用,如果在 Activity.onCreate 中或者其他任何在主线程中执行的代码中调用,当然会,因为主线程进入死循环了。但如果在 ActivityThread 的 main 函数中(这也是这个问题被提出的场景),当然不会。

正是这个无限循环不断地从 MessageQueue 中取出消息,并进行处理,安卓系统的事件循环模型才得以运行。它会导致某个消息的处理时间过长,后面的消息不能及时处理吗?当然不会,没有它都谈不上消息处理!

4. 绘制、点击事件、Activity 生命周期和 Handler 有什么关系?

普通 View 的绘制、点击事件的回调、Activity/Fragment 的生命周期函数,都在主线程执行,这一点大家都知道,但它们是怎么执行在主线程上的?所谓主线程,就是 ActivityThread.main 执行的那个线程,它其实一直在一个无限循环里,上面说过的,还记得吗?那这些代码的执行,怎么会发生在主线程呢,主线程不是在死循环里吗?

其实上述的各种代码,都是由事件触发的,各种事件被发送到了主线程的 Handler 上,然后相应的处理代码就在主线程上执行了。至于事件具体怎么触发的,就超出这个问题的范围了。

5. handler.post, handler.handleMessage 分别执行在什么线程?

handler.post 当然执行在调用这个函数的线程里,handler.handleMessage 则发生在 new Handler 时的线程,也就是这个 Handler 所对应(或者说绑定)的 Looper 所在的那个线程。

这个问题其实没多大价值,但很多人会搞不清对象、函数、代码、运行、线程这些概念的区别,会问出类似下面的问题:

记住一句话:代码会在某个线程运行。Activity 是一个类,某个 Activity 实例一是个对象,类和对象能运行吗?并不能。能运行的只是函数,main 函数也是函数,对吧?

那怎么理解 App 的运行,Activity 的运行?其实就是主进程的主线程一直在跑着事件循环,响应各种事件!

6. Handler 何时会导致 Activity 泄露?

如果使用了非静态的内部 Handler 子类、匿名 Handler 子类,或者把非静态的内部 Runnable 子类、匿名 Runnable 子类 post 到任意 Handler 上,就很可能发生内存泄漏,而如果这些类都在 Activity 内部,那就泄露了 Activity。

搞清楚概念最重要,什么是内存泄漏?只要一个对象应该被 gc 的时候,仍不能被 gc,它就被泄漏了。注意是 不能,而不是 没有。Activity 何时应该被 gc?onDestroy 函数执行之后,就应该被 gc。

内存泄漏是不是一定很严重?看情况,如果只是泄漏了很短的时间,例如 10ms,基本上不会导致问题,当然这也得看情况。但由于 Activity 对象涉及很大的内存空间,一旦泄漏且产生问题,通常都很严重。

Activity 出了上述情形会发生泄漏,还有各种情况,还包括安卓系统的 bug 导致的。引入 LeakCanary 吧,众生百态,总能让你意外,也不必强迫症,非要消除所有被发现的内存泄漏

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

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退出循环。

发表评论

电子邮件地址不会被公开。 必填项已用*标注