Handler消息机制关键类详解
Posted 小陈乱敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler消息机制关键类详解相关的知识,希望对你有一定的参考价值。
概述
什么是Handler?
Handler只是Handler机制中的一个角色。只是我们对Handler接触比较多,所以经常以Handler来代称。
Handler机制是android中基于单线消息队列模式的一套线程消息机制。
他的本质是消息机制,负责消息的分发以及处理。这样讲可能有点抽象,不太容易理解。什么是“单线消息队列模式”?什么是“消息”?
放什么消息以及怎么处理消息,是需要我们去自定义的。Handler机制相当于提供了这样的一套模式,我们只需要“放消息到流水线上”,“编写这些消息的处理逻辑”就可以了,流水线会源源不断把消息运送到末端处理。最后注意重点:每个线程只有一个“流水线”,他的基本范围是线程,负责线程内的通信以及线程间的通信。每个线程可以看成一个厂房,每个厂房只有一个生产线。
两个关键问题
了解Handler的作用前需要了解Handler背景下的两个关键问题:
不能在非UI创建线程去操作UI
不能在主线程执行耗时任务
为什么我们一直都说是非主线程不能更新ui?这是因为我们的界面一般都是由主线程进行绘制的,所以界面的更新也就一般都限制在主线程内。这个异常是在viewRootIimpl.checkThread()方法中抛出来的,那可不可以绕过他?当然可以,在他还没创建出来的时候就可以偷偷更新ui了。阅读过Activity启动流程的读者知道,ViewRootImpl是在onCreate方法之后被创建的,所以我们可以在onCreate方法中创建个子线程偷偷更新UI。(Actvity启动流程解析传送门)但还是那句话,可以,但没必要去绕过这个限制,因为这是谷歌为了我们的程序更加安全而设计的。
为什么不能在子线程去更新UI?因为这会让界面产生不可预期的结果。例如主线程在绘制一个按钮,绘制一半另一个线程突然过来把按钮的大小改成两倍大,这个时候再回去主线程继续执行绘制逻辑,这个绘制的效果就会出现问题。所以UI的访问是决不能是并发的。但,子线程又想更新UI,怎么办?加锁。加锁确实可以解决这个问题,但是会带来另外的问题:界面卡顿。锁对于性能是有消耗的,是比较重量级的操作,而ui操作讲究快准狠,加锁会让ui操作性能大打折扣。那有什么更好的方法?Handler就是解决这个问题的。
第二个问题,不能在主线程执行耗时操作。耗时操作包括网络请求、数据库操作等等,这些操作会导致ANR(Application Not Responding)。这个是比较好理解的,没有什么问题,但是这两个问题结合起来,就有大问题了。数据请求一般是耗时操作,必须在子线程进行请求,而当请求完成之后又必须更新UI,UI又只能在主线程更新,这就导致必须切换线程执行代码,上面讨论了加锁是不可取的,那么Handler的重要性就体现出来了。
不用Handler可不可以?可以,但没必要。Handler是谷歌设计来方便开发者切换线程以及处理消息,然后你说我偏不用,我自己用Java工具类,自己弄个出来不可以吗?那。。。请收下小的膝盖。
为什么要有Handler?
先给结论:
切换代码执行的线程
按顺序规则地处理消息,避免并发
阻塞线程,避免让线程结束
延迟处理消息
第一个作用是最明显也是最常用的,上一部分已经讲了Handler存在的必要性,android限制了不能在非UI创建线程去操作UI,同时不能在主线程执行耗时任务,所以我们一般是在子线程执行网络请求等耗时操作请求数据,然后再切换到主线程来更新UI。这个时候就必须用到Handler来切换线程了。上面讨论过了这里不再赘述。
这里有一个误区是:我们的activity是执行在主线程的,我们在网络请求完成之后回调主线程的方法不就切换到主线程了吗?咳咳,不要笑,不要觉得这种低级错误太离谱,很多童鞋刚开始接触开发的时候都会犯这个思维错误。这其实是理解错了线程这个概念。代码本身并没有限制运行在哪个线程,代码执行的线程环境取决于你的执行逻辑是在哪个线程。这样讲可能还是有点抽象。例如现在有一个方法void test(),然后两个不同的线程去调用它:
new Thread()
// 第一个线程调用
test();
.start();
new Thread()
// 第二个线程调用
test();
此时虽然都是test这个方法,但是他的执行逻辑是由不同的线程调用的,所以他是执行在两个不同的线程环境下。而当我们想要把逻辑切换到另一个线程去执行的时候,就需要用到Handler来切换逻辑。
第二个作用可能看着有点懵。但其实他解决了另一个问题:并发操作。虽然切换线程解决了,如果主线程正在绘制一个按钮,刚测量好按钮的长宽,突然子线程一个新的请求过来打断了,先停下这边的绘制操作,把按钮改成了两倍大,然后逻辑切回来继续绘制,这个时候之前的测量的长宽已经是不准确的了,绘制的结果肯定也不准确。怎么解决?单线消息队列模型。在讲什么是Handler那部分简单介绍过,就是相当于一个流水线一样的模型。子线程的请求会变成一个个的消息,然后主线程依次处理,那么就不会出现绘制一半被打断的问题了。
同时这种模型也不止用于解决ui并发问题,在ActivityThread中有一个H类,他其实就是个Handler。在ActivityThread中定义了一百多中消息类型以及对应的处理逻辑,这样,当需要让ActivityThread处理某一个逻辑的时候,只需要发送对应的消息给他即可,而且可以保证消息按顺序执行,例如先调用onCreate再调用onResume。而如果没有Hanlder的话,就需要让ActivityThread有一百多个接口对外开放,同时还需要不断进行回调保证任务按顺序执行。这显然复杂了非常多。
我们执行一个Java程序的时候,从main方法入口,执行完成之后,马上就退出了,但是我们android应用程序肯定是不可以的,他需要一直等待用户的操作。而Handler机制就解决了这个问题,但消息队列中没有任务的时候,他就会把线程阻塞,等到有新的任务的时候,再重新启动处理消息。
第四个作用让延迟处理消息得到了最佳解决方案。假如你想让应用启动5秒后界面弹出一个对话框,没有handler的情况下,会如何处理?开一个Thread然后使用Thread.sleep让线程睡眠一对应的时间对吧,但如果多个延迟任务呢?而开启线程也是个比较重量级的操作且线程的数量有限。而可以直接给Handler发送延迟对应时间的消息,他会在对应时间之后准时处理该消息(当然有特殊情况,如单件消息处理时间过长或者同步屏障,后面会讲到)。而且无论发送多少延迟消息都不会对性能有任何影响。同时,也是通过这个功能来记录ANR的时间。
讲这些作用可能读者心中并没有一个很形象的概念,也可能看完就忘了。但是关于Handler的定义不能忘:Handler机制是Android中基于单线消息队列模式的一套线程消息机制。上述四个作用是为了让读者更好地理解Handler机制。
如何使用Handler
我们平常使用Handler有两种不同的创建方式,但总体流程是相同的:
创建Looper
使用Looper创建Handler
启动Looper
使用Handler发送信息
Looper可理解为循环器,就像“流水线”上的滚带,后面会详细讲到。每个线程只有一个Looper,通常主线程已经创建好了,追溯应用程序启动流程可以知道启动过程中调用了Looper.prepareMainLooper,而在子线程就必须使用如下方法来初始化Looper:
Looper.prepare();
第二步是创建Handler,也是最熟悉的一步。我们有两种方法来创建Handler:传入callBack对象和继承。如下:
public class MainActivity extends AppComposeActivity
...;
// 第一种方法:使用callBack创建handler
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
Handler handler = Handler(Looper.myLooper(),new CallBack()
public Boolean handleMessage(Message msg)
TODO("Not yet implemented")
);
// 第二种方法:继承Handler并重写handlerMessage方法
static MyHandler extends Hanlder
public MyHandler(Looper looper)
super(looper);
@Override
public void handleMessage(Message msg)
super.handleMessage(msg);
// TODO(重写这个方法)
注意第二种方法,要使用静态内部类,不然可能会造成内存泄露。原因是非静态内部类会持有外部类的引用,而Handler发出的Message会持有Handler的引用。如果这个Message是个延迟的消息,此时activity被退出了,但Message依然在“流水线”上,Message->handler->activity,那么activity就无法被回收,导致内存泄露。
两种Handler的写法各有千秋,继承法可以写比较复杂的逻辑,callback法适合比价简单的逻辑,看具体的业务来选择。
然后再调用Looper的loope方法来启动Looper:
Looper.loop();
最后就是使用Handler来发送信息了。当我们获得handler的实例之后,就可以通过他的sendMessage相方法和post相关方法来发送信息,如下:
handler.sendMessage(msg);
handler.sendMessageDelayed(msg,delayTime);
handler.post(runnable);
handler.postDelayed(runnable,delayTime);
然后一般情况下是哪个Handler发出的信息,最终由哪个Handler来处理。这样,只要我们拿到Handler对象,就可以往对应的线程发送信息了。
Handler内部模式结构
经过前面的介绍对于Looper已经有了一定的认知,但可能对他内部的模式还不太清楚。这一部分先讲解Handler的大概内部模式,目的是为下面的详解做铺垫,为做整体概念感知。先上图:
Handler机制内部有三大关键角色:Handler,Looper,MessageQueue。其中MessageQueue是Looper内部的一个对象,MessageQueue和Looper每个线程有且只有一个,而Handler是可以有很多个的。他们的工作流程是:
用户使用线程的Looper构建Handler之后,通过Handler的send和post方法发送消息
消息会加入到MessageQueue中,等待Looper获取处理
Looper会不断地从MessageQueue中获取Message然后交付给对应的Handler处理
这就是大名鼎鼎的Handler机制内部模式了,说难,其实也是很简单。
以上是关于Handler消息机制关键类详解的主要内容,如果未能解决你的问题,请参考以下文章