Handler 机制分析

android 子线程和UI线程的交互主要使用Handler的方法进行通信。本文分析Handler机制

Handler 如何使用?

Handler的使用比较简单

    public class MainActivity extends Activity{private Handler handler = new Handler() {  public void handleMessage(Message msg) {   switch (msg.what) {   case 0x01://do somethings}}};@Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  handler = new Handler();new Thread(new Runnable(){@Overridepublic void run(){Message message = new Message();   message.what = 0x01;   handler.sendMessage(message);}}).start();}}复制代码

如代码就是一个简单的Handler的使用Demo,有如下几个问题

  1. Handler 是否可以在子线程中初始化。可以,但是如下代码执行的话会抛出该错误"Can't create handler inside thread that has not called Looper.prepare() ".说
    是不能再没有调用Looper.prepare()的线程中创建Handler。因此如果需要在线程中创建Handler首先调用一下Looper.prepare
    new Thread(new Runnable() {  @Override  public void run() {  Handler handler = new Handler();  }  }).start();复制代码

这样调用将不会抛出异常。

    new Thread(new Runnable() {  @Override  public void run() {Looper.prepare();Handler handler = new Handler();  Looper.loop();}  }).start();复制代码

Looper和Handler的联系是什么样的呢?

我们看一下Handler初始化的代码

    public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}复制代码

可以看到如果mLooper是通过Looper.myLooper获得一个Looper对象,如果Looper对象为空,则抛出上述异常。那Looper.myLooper是如何定义的呢?

     public static @Nullable Looper myLooper() {return sThreadLocal.get();}复制代码

该方法很简单就是从sThreadLocal对象中获得Looper对象。如果sThreadLocal中存在就返回Looper,如果没有就返回null。那Looper是如何存放在sThreadLocal中,
不错就是Looper.prepare。

      private 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));}复制代码

可以看到,首先判断sThreadLocal中是否存在Looper对象,如果已经存在,那么如果还有prepare Looper则抛出异常;否则就新建一个Looper存放到sThreadLocal中。
该代码同时说明每个线程最多一个Looper对象。

Looper采用ThreadLocal来维护各个线程的Looper对象。ThreadLocal是什么呢?官方定义是:ThreadLocal实现了线程本地存储。所有线程共享同一个ThreadLocal对象,但不同线程仅能访问与其线程相关联的值,一个线程修改ThreadLocal对象对其他线程没有影响。
我们可以将ThreadLocal理解为一块存储区,将这一大块存储区分割为多块小的存储区,每一个线程拥有一块属于自己的存储区,那么对自己的存储区操作就不会影响其他线程。对于ThreadLocal,则每一小块存储区中就保存了与特定线程关联的Looper。

主线程中使用Handler时为什么没有执行Looper.prepare()也可以使用Handler呢?其实在进程启动的时候我们已经创建了主线程也依赖的Looper,代码在ActivityThread中main方法中。

     public static void main(String[] args) {....Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}复制代码

可以看到该方法执行的是Looper.prepareMainLooper方法,可看到归根到底还是执行prepare方法

    public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}复制代码

Message又是如何引入到Handler机制的?

众所周知,我们都知道Handler,Message,Looper是Handler机制不可或缺的要素。那么Message都是如何引入到Handler.我们看一下上述例子Message是通过handler.sendMessage(message)引入到Handler中。

      public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}复制代码

可以看到sendMessage最终调用MessageQueue中enqueueMessage

    boolean enqueueMessage(Message msg, long when) {....msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}复制代码

MessageQueue并没有使用一个集合把信息保存,它只是通过使用mMessage对象表示当前需要处理消息,然后根据时间把msg进行排序。具体方法是根据时间顺序调用msg.next。从而为每一个消息指定它
的下一个消息是什么。如果需要将msg作为队头插入到MessageQueue中可以调用sendMessageAtFrontOfQueue实现。

这样消息就进入到MessageQueue中,那如何从MessageQueue中将消息取出来呢?
大家有没有注意到Loop.prepare一般和Looper.loop对应使用。其实Looper.loop就是用来从MessageQueue中取出message。

    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;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {msg.target.dispatchMessage(msg);} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}....}}复制代码

上文我们知道每个Thread都有一个Looper,其实每个Looper都对应一个MessageQueue。loop方法我们获得对应looper中的MessageQueue不断取出msg,并传入到dispatchMessage.
dispatchMessage方法将取出的msg传递到定义Handler时重写的handleMessage方法。

    public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}复制代码

Handler,Message,MessageQueue,Looper流程示意图如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/287733.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gearman mysql编译_gearman初探(一、编译和安装)

gearman是一个任务分发系统&#xff0c;将计算比较耗时的任务分发给不同的机器专门进行计算&#xff0c;而任务发起的初始程序不必等待这些任务完成就可以返回而继 续执行。笔者最开始做PHP邮件发送的时候&#xff0c;因为邮件发送耗时比较长&#xff0c;这时PHP脚本就会被阻塞…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(四)了解魔法百宝箱列表、字典及基本数据类型

学习目标 了解魔法世界中可更改容器——变量了解魔法世界的基本数值类型——字符串、整数了解魔法百宝箱——字典、列表了解列表如何添加值了解字典如何添加值了解字典与列表定义的不同符号 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&…

TCP协议之如何保证传输的可靠性

一、问题 TCP协议之如何保证传输的可靠性?我们先看下TCP的头部图片和TCP头部的字段 /*TCP头定义,共20个字节*/ typedef struct _TCP_HEADER {short m_sSourPort;       // 源端口号16bitshort m_sDestPort;       // 目的端口号16bitunsigned int …

【工具篇】在Mac上开发.Net Core需要的工具

微信公众号&#xff1a;趣编程ACE关注可了解更多的.NET日常开发技巧,如需帮助&#xff0c;请后台留言&#xff1b;[如果觉得本公众号对您有帮助&#xff0c;欢迎关注]在Mac上开发.Net Core需要的工具如果您是一个.NET 开发者&#xff0c;想从Windows切换到Mac上开发.NET或者您已…

【Pix4d精品教程】Pix4Dmapper完整航测内业操作流程手把手图文教程

1. 作业流程图 2. 原始资料准备 原始资料包括影像数据、POS数据以及控制点数据。 确认原始数据的完整性,检查获取的影像中有没有质量不合格的相片。同时查看POS数据文件,主要检查航带变化处的相片号,防止POS数据中的相片号与影像数据相片号不对应,出现不对应情况应手动调…

关于构造函数和this调用的思考

文中一系列思考和内容引发自以下问题&#xff1a;我需要在一个类的构造函数中调用另一个对象的构造函数&#xff0c;并使用this初始化其中的一个引用成员。 主要遇到的问题&#xff1a; 1. 构造函数的初始化列表中能访问this吗&#xff1f; 很明显c创建一个对象分为两部分&…

mysql semi join_MySQL 5.6 Semi join优化之materialization strategy

8月 24, 2014 |Nix.Huang考虑如下查询&#xff1a;select * from Countrywhere Country.code IN (select City.Countryfrom Citywhere City.Population > 7*1000*1000)and Country.continentEurope这个子查询是非相关子查询&#xff0c;我们能和外层循环独立的执行它&#x…

【ArcGIS风暴】何为动态投影?这次全面为您揭开ArcGIS中动态投影的神秘面纱!

本课程配套蓝光视频: 【ArcGIS风暴】GIS动态投影问题 GISer们都见过以下警告,该警告的原因是当前加载的数据的坐标系和当前数据框坐标系不一致导致的,核心问题是地理坐标系不一致。如当前数据的坐标系是GCS_Xian_1980,而数据框的坐标系有可能是WGS_1984等,总之跟要加载的数…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(五)我的魔法竟然有了一丝逻辑

学习目标 了解魔法世界中的结构表现——缩进了解魔法世界的逻辑判断——if了解魔法世界的多次逻辑判断——ifelse嵌套了解魔法世界中的逻辑运算——且 and 与或 or 推荐 1.《备受好评的看得懂的C语言入门教程》 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的P…

类和类之间的关系

一、类和类之间的关系 UML类图中&#xff0c;常见以下几种关系: 1、泛化&#xff08;Generalization&#xff09; 是一种继承关系&#xff0c;比如动物类和老虎类&#xff0c;老虎继承动物&#xff0c;子类如何特化父类的所有特征和行为 箭头指向:带三角箭头的实线&#xff0…

Java SpringMvc+hibernate架构中,调用Oracle中的sp,传递数组参数

一、问题 我们调用数据&#xff0c;大都是可以直接获取表中的数据&#xff0c;或者用复杂点的sql语句组成的。但是&#xff0c;有时候&#xff0c;当这样达不到我们要的全部数据的时候&#xff0c;这时&#xff0c;我们就用到了存储过程【sp】&#xff0c;如果sp需要参数是数组…

js模拟blur

<div></div> 某个事件给div加个属性 $(div).attr(wait,true); $(div).click(function() { if(false!$(this).attr(wait)) return false; })转载于:https://www.cnblogs.com/cndxk/p/4788414.html

中国第一朵企业云

本文讲的是中国第一朵企业云&#xff0c;【IT168 资讯】算起来&#xff0c;中国中化集团公司的ERP完全运行在“云”上已经一个多月了&#xff0c;每每提到这个“创举”&#xff0c;信息技术部总经理彭劲松显得有些兴奋&#xff0c;却仍然很谨慎。作为中国第一家企业云的实践者&…

查缺补漏系统学习 EF Core 6 - 实体配置

推荐关注「码侠江湖」加星标&#xff0c;时刻不忘江湖事这是 EF Core 系列的第二篇文章&#xff0c;上一篇文章讲解了 EF Core 的一些基础概念&#xff0c;这一篇文章主要围绕实体属性的配置。点击上方或后方蓝字&#xff0c;阅读 EF Core 系列合集。实体配置配置实体的目的&am…

【ArcGIS风暴】捕捉VS经典捕捉,谁更有用武之地?

几乎所有的GIS软件都具有捕捉功能!今天我们一起来聊一聊ArcGIS软件中的捕捉功能吧。 ArcGIS软件中有两个重要的捕捉工具:捕捉和经典捕捉。 目录 一、捕捉(Snapping) 1、捕捉类型 2、捕捉选项

mysql innodb 索引 延迟更新_Mysql覆盖索引与延迟关联

延迟关联&#xff1a;通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。为什innodb的索引叶子节点存的是主键&#xff0c;而不是像myisam一样存数据的物理地址指针&#xff1f;如果存的是物理地址指针不就不需要二次查找了吗&#xff0c;根据myisam和inn…

Android之在笔记本电脑adb devices识别不了oppo A9手机(设备管理器ADB Interface里面有个黄色感叹号)

1 问题 记本电脑adb devices识别不了oppo A9手机&#xff08;设备管理器ADB Interface里面有个黄色感叹号&#xff09; 图片如下 2 分析 很明显这里是驱动问题&#xff0c;ADB Interface有感叹号提示&#xff0c;所以这里需要解决驱动问题 3 解决办法 1&#xff09;可以尝试…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(六)精简魔法更强大

学习目标 了解对相似逻辑的简化编写——循环 推荐 1.《备受好评的看得懂的C语言入门教程》 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&#xff08;一&#xff09;既然你选择了这系列教程那么我就要让你听得懂 第二篇&#xff1a;《假…

Tiny模板语言(VelocityPlus)初步入门

2019独角兽企业重金招聘Python工程师标准>>> 1 关于用户手册 本文主要介绍如何在模板中使用Tiny模板语言&#xff0c;通过查阅本手册&#xff0c;可以对Tiny模板语言 TTL(Tiny Template Language)的用法有一个较全面的认识&#xff0c;并学会如何有效地使用Tiny模板…

第十二周学习进度表

第十二周所花时间&#xff08;包括上课&#xff09;上课&#xff1a;4小时&#xff08;2小时的实验&#xff09;&#xff0c;周一&#xff1a;2小时&#xff0c;周三&#xff1a;3小时&#xff0c;周四&#xff1a;2小时&#xff0c;周五&#xff1a;2小时&#xff0c;周六、日…