Handler是Android中提供的一种异步回调机制,也可以理解为线程间的消息机制。为了避免ANR,我们通常会把一些耗时操作(比如:网络请求、I/O操作、复杂计算等)放到子线程中去执行,而当子线程需要修改UI时则子线程需要通知主线程去完成修改UI的操作,则此时就需要我们使用Handler机制来完成子线程与主线程之间的通信。

1. Handler的一般使用步骤

在明确了Android中只有主线程能修改UI界面、子线程执行耗时操作的前提后,下面一起来学习下Handler的使用步骤。

  1. 在主线程中创建Handler实例,并且重写handlerMessage方法。

    private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {switch (msg.what) {case 1://执行相关修改UI的操作break;}}
    };
    
  2. 子线程中获取Handler对象,在需要执行更新UI操作的地方使用handler发送消息

    Message msg = Message.obtain();
    msg.obj = "content";
    msg.what = 1;
    //发送消息给Handler
    handler.sendMessage(msg);
    
  3. 在handlerMessage方法中的Switch里面,根据case下不同的常量执行相关操作

以上只是Handler的一种使用方式,由于本文的重点是探究Handlerde原理,故其他使用方式这里不重点介绍。

2. 重要类的职责

在深入了解Handler机制原理之前,我们应该明确在Handler机制中几个重要类的职责。

  • Handler:负责发送处理消息
  • MessageQueue:消息队列,负责存储消息
  • Message: 具体发送的消息
  • Looper: 负责循环取出消息给Handler处理
  • ThreadLocal: 用于线程间的数据隔离,在每个线程中存放各自对应的Looper

3. Handler机制原理

  • 每个Handler都会关联一个消息队列,消息队列又是封装在Looper对象中,而每个Looper又会关联一个线程。这样Handler、消息队列、线程三者就关联上了。

  • Handler是一个消息处理器,将消息发送给消息队列,然后再由对应的线程从消息队列中逐个取出,并执行。

  • 默认情况下,消息队列只有一个,也就是主线程的消息队列,该消息队列通过Looper.prepareMainLooper()方法创建,最后执行Looper.loop()来循环启动消息。

以上叙述可能有点空洞,下面我们结合源码一起来理解:

3.1 Handler是如何关联消息队列和线程的?

我们首先看Handler默认的构造函数:

     public Handler(Callback callback, boolean async) {//代码省略mLooper = Looper.myLooper();//获取Looperif (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;//通过Looper对象获取消息队列mCallback = callback;mAsynchronous = async;}//获取Looper对象public final Looper getLooper() {return mLooper;}

从Handler的构造函数中我们可以发现,Handler在初始化的同时会通过Looper.getLooper()获取一个Looper对象,并与Looper进行关联,然后通过Looper对象获取消息队列。那我们继续深入到Looper源码中去看Looper.getLooper()是如何实现的。

    //初始化当前线程Looperpublic static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}//为当前线程设置一个Looperprivate static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));}

从上面的程序可以看出通过prepareMainLooper(),然后调用 prepare(boolean quitAllowed)方法创建了一个Looper对象,并通过sThreadLocal.set(new Looper(quitAllowed))方法将该对象设置给了sThreadLocal。

    //通过ThreadLocal获取Looperpublic static @Nullable Looper myLooper() {return sThreadLocal.get();}

通过Looper中的预备工作,sThreadLocal中已经存储了一个Looper对象,然后myLooper()方法通过sThreadLocal.get()方法获取到了Looper。那么消息队列就与线程关联上了,所以各个线程只能访问自己的消息队列。

综上所述,我们可以发现消息队列通过Looper与线程关联上了,而Looper又与Handler是关联的,所以Handler就跟线程、线程的消息队列关联上了。

3.2 如何执行消息循环?

在创建Looper对象后,通过Handler发来的消息放在消息队列中后是如何被处理的呢?这就涉及到了消息循环,消息循环是通过Looper.loop()方法来建立的。源代码如下:

    //执行消息循环public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;//获取消息队列//代码省略for (;;) {//死循环Message msg = queue.next(); // 获取消息if (msg == null) {// No message indicates that the message queue is quitting.return;}//代码省略try {msg.target.dispatchMessage(msg);//分发消息} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}//代码省略msg.recycleUnchecked();//消息回收}}

从源码中我们可以看出,loop()方法实质上就是通过一个死循环不断的从消息队列中获取消息,然后又不断的处理消息的过程。

3.3 消息处理机制
msg.target.dispatchMessage(msg);//分发消息

我们从loop中的 dispatchMessage()方法入手,看看谁是该方法的调用者,深入Message源码中看看target的具体类型:

    public final class Message implements Parcelable {//代码省略/*package*/ int flags;/*package*/ long when;/*package*/ Bundle data;/*package*/ Handler target;/*package*/ Runnable callback;/*package*/ Message next;//代码省略}

从源码中我们可以看到其实target就是Handler类型。所以Handler是将消息发送到消息队列暂时存储下,然后又将消息发送给Handler自身去处理。那我们继续到Handler源码中去看看Handler是如何处理消息的:

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
private static void handleCallback(Message message) {message.callback.run();
}
/*** Subclasses must implement this to receive messages.* 消息处理方法为一个空方法,由子类去实现*/
public void handleMessage(Message msg) {
}

从上面的源码中可以看出,dispatchMessage(Message msg)只负责分发Message。从Message源码中我么可以知道callback为Runnable类型,如果callback不为空,则执行 handleCallback方法来处理,而该方法又会调用callback.run();如果如果callback为空,则调用handleMessage来处理消息,而该方法又为空,所以我们会在子类中重写该方法,并将修改UI的代码写在里面。之所以会出现这两种情况,是因为Handler发送消息有两种形式:

  • 在本文的一般使用步骤中,我使用的是sendMessage(msg)发送消息,此时callback就为空。
  • 当使用post发送消息时,callback就不为空。

以上就是Handler机制的原理,大致可以总结为:在子线程中Handler将消息发送到MessageQueue中,然后Looper不断的从MessageQueue中读取消息,并调用Handler的dispatchMessage发送消息,最后再Handler来处理消息。为了更好的帮助大家一起理解,我画了一个Handler机制的原理图:

关于Handler机制补充如下几点:

  • Handler创建消息时用到了消息池,在创建消息时会先从消息池中去查询是否有消息对象,如果有,则直接使用消息池中的对象,如果没有,则创建一个新的消息对象。
  • 使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper

以上就是我对Handler机制原理的理解,希望对你有所帮助。如有不足之处,欢迎指正!

浅谈Android中的Handler机制相关推荐

  1. 浅谈Android中的MVP与动态代理的结合

    浅谈Android中的MVP与动态代理的结合 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 在Android开发平台上接触MVP足足算起来大概已经有一个年头左右.从最开始到现在经 ...

  2. Android中的Handler机制

    直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错 误:android.view.ViewRoot$CalledFromWrongThreadException: ...

  3. android handler的机制和原理_第一百八十回:Android中的Handler机制九

    各位看官们大家好,上一回中咱们说的是Android中Handler机制的例子,这一回咱们继续说该例子.闲话休提,言归正转.让我们一起Talk Android吧! 看官们,由于时间的原因我们在上一回中只 ...

  4. 浅谈android中手机联系人字母索引表的实现

    实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起 ...

  5. android中handler机制,如何使用?,Android中的Handler机制

    一.Handler概述 二.Handler发送消息的方法 三.MessageQueue的enqueueMessage() 四.Message的when字段 五.子线程中使用Handler 六.Loop ...

  6. android怎么用代码调图像,浅谈android中图片处理之图形变换特效Matrix(四)(示例代码)...

    今天,我们就来谈下android中图片的变形的特效,在上讲博客中我们谈到android中图片中的色彩特效来实现的.改变它的颜色主要通过ColorMatrix类来实现. 现在今天所讲的图片变形的特效主要 ...

  7. 浅谈android中图片处理之图形变换特效Matrix(四)

    今天,我们就来谈下android中图片的变形的特效,在上讲博客中我们谈到android中图片中的色彩特效来实现的.改变它的颜色主要通过ColorMatrix类来实现. 现在今天所讲的图片变形的特效主要 ...

  8. 浅谈android中加载高清大图及图片压缩方式(二)

    这一讲就是本系列的第二篇,一起来聊下关于android中加载高清大图的问题,我们都知道如果我们直接加载原图的话,一个是非常慢,需要等待一定时间,如果没有在一定的时间内给用户响应的话,将会极大影响用户的 ...

  9. 浅谈android中的图片处理之基本绘图(一)

    从今天开始我来聊下关于android中的图片处理以及android中绘图的基本用法.大家都知道android中的图片的巧妙使用会给UI得到一个很好的用户体验.所以掌握处理图片的基本技巧很是重要.那就开 ...

最新文章

  1. SpringMVC解决前台传入的数组或集合类型数据
  2. 机器学习 数据增加_【机器学习】数据降维概述
  3. loadrunner中面向目标场景的设计
  4. 阶段3 3.SpringMVC·_07.SSM整合案例_01.ssm整合说明
  5. 用DVD镜像离线安装Debian的软件包
  6. Java jdk API 1.8英文版、中文版分享
  7. diabetes影响因子2017_【重磅】2017 年内分泌领域 SCI 期刊影响因子出炉
  8. [fsevents@^1.2.2] optional install error: Package require os(darwin) not compatible with your platfo
  9. 服务器协议失败,Chrome中的HTTPS服务器“不支持的协议错误”
  10. CJT长江连接器A1276系列线对板连接器排针排母PCB封装库
  11. RobotFramework学习笔记二:遇到Frame框架
  12. 无线耳机的分类和技术特点
  13. Linux Socket 两个客户端通信,服务端作为中转
  14. C++ 头文件系列(set)
  15. 获得了第一枚勋章---CSDN持之以恒勋章
  16. 告别最好用的微软UWP应用OneNote 微软已经开始将其替换为Win32版
  17. Open edX数据结构Mysql edxapp
  18. Redis精通系列——Stream
  19. python tableau工作流_提取数据
  20. [软件笔记------AutoCAD 2007]

热门文章

  1. Facebook向量召回双塔模型
  2. 一文弄懂各种loss function
  3. 实时通信RTC技术栈之:视频编解码
  4. spring框架 AOP核心详解
  5. 邮件MIME格式分析
  6. 运营商部署4.5G热情高涨:将影响5G商用
  7. Maven编译时提示:不兼容的类型
  8. Extending_and_embedding_php翻译
  9. 片上网络NoC(一)—— 概述
  10. 在已有SQL 2005 Server 群集中添加节点