目录介绍

  • 6.0.0.1 谈谈消息机制Hander作用?有哪些要素?流程是怎样的?
  • 6.0.0.2 为什么一个线程只有一个Looper、只有一个MessageQueue,可以有多个Handler?
  • 6.0.0.3 可以在子线程直接new一个Handler吗?会出现什么问题,那该怎么做?
  • 6.0.0.4 Looper.prepare()能否调用两次或者多次,会出现什么情况?
  • 6.0.0.5 为什么系统不建议在子线程访问UI,不对UI控件的访问加上锁机制的原因?
  • 6.0.0.6 如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)
  • 6.0.0.7 Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?
  • 6.0.0.8 Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。
  • 6.0.0.9 Message可以如何创建?哪种效果更好,为什么?
  • 6.0.1.3 使用Hanlder的postDealy()后消息队列会发生什么变化?
  • 6.0.1.4 ThreadLocal有什么作用?

好消息

  • 博客笔记大汇总【15年10月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计500篇[近100万字],将会陆续发表到网上,转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有的笔记将会更新到GitHub上,同时保持更新,欢迎同行提出或者push不同的看法或者笔记!

6.0.0.1 谈谈消息机制Hander作用?有哪些要素?流程是怎样的?

  • 作用:

    • 跨线程通信。当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
  • 四要素:
    • Message(消息):需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。技术博客大总结
    • MessageQueue(消息队列):用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
    • Handler(处理者):负责Message的发送及处理。通过 Handler.sendMessage() 向消息池发送各种消息事件;通过 Handler.handleMessage() 处理相应的消息事件。
    • Looper(消息泵):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。
  • 具体流程
    • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息;
    • 通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next();
    • 调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。

6.0.0.2 为什么一个线程只有一个Looper、只有一个MessageQueue,可以有多个Handler?

  • 注意:一个Thread只能有一个Looper,可以有多个Handler

    • Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue。
  • 为什么一个线程只有一个Looper?技术博客大总结
    • 需使用Looper的prepare方法,Looper.prepare()。可以看下源代码,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。
    • 所以一个线程只有一个Looper,不知道这样解释是否合理!更多可以查看我的博客汇总:https://github.com/yangchong211/YCBlogs
    public static void prepare() {
    prepare(true);
    } 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));
    }

6.0.0.3 可以在子线程直接new一个Handler吗?会出现什么问题,那该怎么做?

  • 不同于主线程直接new一个Handler,由于子线程的Looper需要手动去创建,在创建Handler时需要多一些方法:

    • Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。
    • 正确的使用方法是:技术博客大总结
    handler = null;
    new Thread(new Runnable() {
    private Looper mLooper;
    @Override
    public void run() {
    //必须调用Looper的prepare方法为当前线程创建一个Looper对象,然后启动循环
    //prepare方法中实质是给ThreadLocal对象创建了一个Looper对象
    //如果当前线程已经创建过Looper对象了,那么会报错
    Looper.prepare();
    handler = new Handler();
    //获取Looper对象
    mLooper = Looper.myLooper();
    //启动消息循环
    Looper.loop();
    //在适当的时候退出Looper的消息循环,防止内存泄漏
    mLooper.quit();
    }
    }).start();
  • 主线程中默认是创建了Looper并且启动了消息的循环的,因此不会报错:应用程序的入口是ActivityThread的main方法,在这个方法里面会创建Looper,并且执行Looper的loop方法来启动消息的循环,使得应用程序一直运行。

6.0.0.4 Looper.prepare()能否调用两次或者多次,会出现什么情况?

  • Looper.prepare()方法源码分析

    • 可以看到Looper中有一个ThreadLocal成员变量,熟悉JDK的同学应该知道,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    public static void prepare() {
    prepare(true);
    } 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));
    }
  • 思考:Looper.prepare()能否调用两次或者多次
    • 如果运行,则会报错,并提示prepare中的Excetion信息。由此可以得出在每个线程中Looper.prepare()能且只能调用一次
    • 技术博客大总结
    //这里Looper.prepare()方法调用了两次
    Looper.prepare();
    Looper.prepare();
    Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    if (msg.what == 1) {
    Log.i(TAG, "在子线程中定义Handler,并接收到消息。。。");
    }
    }
    };
    Looper.loop();

6.0.0.5 为什么系统不建议在子线程访问UI,不对UI控件的访问加上锁机制的原因?

  • 为什么系统不建议在子线程访问UI

    • 系统不建议在子线程访问UI的原因是,UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。
  • 不对UI控件的访问加上锁机制的原因

6.0.0.7 Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

  • 问题描述

    • 在处理消息的时候使用了Looper.loop()方法,并且在该方法中进入了一个死循环,同时Looper.loop()方法是在主线程中调用的,那么为什么没有造成阻塞呢?
  • ActivityThread中main方法
    • ActivityThread类的注释上可以知道这个类管理着我们平常所说的主线程(UI线程)

      • 首先 ActivityThread 并不是一个 Thread,就只是一个 final 类而已。我们常说的主线程就是从这个类的 main 方法开始,main 方法很简短
      public static final void main(String[] args) {
      ...
      //创建Looper和MessageQueue
      Looper.prepareMainLooper();
      ...
      //轮询器开始轮询
      Looper.loop();
      ...
      }
  • Looper.loop()方法无限循环
    • 看看Looper.loop()方法无限循环部分的代码

      while (true) {
      //取出消息队列的消息,可能会阻塞
      Message msg = queue.next(); // might block
      ...
      //解析消息,分发消息
      msg.target.dispatchMessage(msg);
      ...
      }
  • 为什么这个死循环不会造成ANR异常呢?
    • 因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。技术博客大总结
  • 处理消息handleMessage方法
    • 如下所示

      • 可以看见Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
      • 如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
      public void handleMessage(Message msg) {
      if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
      switch (msg.what) {
      case LAUNCH_ACTIVITY: {
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
      final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
      r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
      handleLaunchActivity(r, null);
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      }
      break;
      case RELAUNCH_ACTIVITY: {
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
      ActivityClientRecord r = (ActivityClientRecord) msg.obj;
      handleRelaunchActivity(r);
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      }
      break;
      case PAUSE_ACTIVITY:
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
      handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
      maybeSnapshot();
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      break;
      case PAUSE_ACTIVITY_FINISHING:
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
      handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      break;
      ...........
      }
      }
  • loop的循环消耗性能吗?
    • 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
    • 简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。

6.0.0.9 Message可以如何创建?哪种效果更好,为什么?runOnUiThread如何实现子线程更新UI?

  • 创建Message对象的几种方式:技术博客大总结

    • Message msg = new Message();
    • Message msg = Message.obtain();
    • Message msg = handler1.obtainMessage();
  • 后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message
  • runOnUiThread如何实现子线程更新UI
    • 看看源码,如下所示
    • 如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,所以handler的handlerMessage方法的执行也会在主线程中。
    • 在runOnUiThread程序首先会判断当前线程是否是UI线程,如果是就直接运行,如果不是则post,这时其实质还是使用的Handler机制来处理线程与UI通讯。
    public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    } @Override
    public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
    mHandler.post(action);
    } else {
    action.run();
    }
    }

6.0.1.3 使用Hanlder的postDealy()后消息队列会发生什么变化?

  • post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

6.0.1.4 ThreadLocal有什么作用?

  • 线程本地存储的功能

    • ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
    • 技术博客大总结
  • 怎么存储呢?底层数据结构是啥?
    • 每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

06.Android之消息机制问题的更多相关文章

  1. Android的消息机制简单总结

    参考文章: http://gityuan.com/2015/12/26/handler-message-framework/#next 参考资料: Android Framework的源码: Mess ...

  2. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

  3. Android的消息机制

    一.简介 ①.我们不能在子线程中去访问UI空控件,这是时候只能通过Handler将更新UI的操作放到主线程中去执行 ②.Handler的组成:messageQueue和Looper的支持 ③.Mess ...

  4. 【原创】源码角度分析Android的消息机制系列(一)——Android消息机制概述

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.为什么需要Android的消息机制 因为Android系统不允许在子线程中去访问UI,即Android系统不允许在子线程中更新UI. 为什 ...

  5. 【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻 ...

  6. 【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其 ...

  7. Android 基础 十一 Android的消息机制

    Handler是Android消息机制的上层接口,这使得在开发应用过程中我们只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去 ...

  8. 聊一聊Android的消息机制

    聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比 ...

  9. Android开发——Android的消息机制详解

    )子线程默认是没有Looper的,Handler创建前,必须手动创建,否则会报错.通过Looper.prepare()即可为当前线程创建一个Looper,并通过Looper.loop()来开启消息循环 ...

  10. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

随机推荐

  1. 《ASP.ENT Core 与 RESTful API 开发实战》-- (第5章)-- 读书笔记(下)

    第 5 章 使用 Entity Framework Core 5.4 重构 Controller 和 Action 重构 AuthorController 构造函数重构 public IMapper ...

  2. Widget模式

    Widget模式 Widget模式是指借用Web Widget思想将页面分解成组件,针对部件开发,最终组合成完整的页面,Web Widget指的是一块可以在任意页面中执行的代码块,Widget模式不属 ...

  3. SpringBoot+Shiro+LayUI权限管理系统项目-9.核心知识点总结

    1.说明 本篇讲一下本项目几个重要的知识点,详细看源码,文章下方捐赠或QQ联系捐赠获取. 2.Shiro如何设置密码加密算法 1.在shiro配置文件中添加: @Bean public HashedC ...

  4. 树莓派/Linux ubuntu 开机自动改网络mac地址(主要适用于拷贝内存卡的情况/不同树莓派mac地址不同)

    树莓派/Linux ubuntu 开机自动改网络mac地址(主要适用于拷贝内存卡的情况/不同树莓派mac地址不同) yaml文件名根据自己原卡中名字更改 address=$(cat /sys/clas ...

  5. python 创建动态类

    一般情况下多数是预先定义类 而少数特殊情况就需要去动态创建类了,直接贴代码. class BaseModel(Model): class Meta: database = _tb class_new ...

  6. win32 - 使用GDI+播放gif图片

    今天做case的时候遇到一个这样的问题,故记录下来. Codeproject有类似的案例,不过是使用的MFC模板编译的. 因为我们只需要win32程序,所以就....代码如下: CodeProject ...

  7. MYSQL查询数据表中某个字段包含某个数值

    当某个字段中字符串是"1,2,3,4,5,6"或者"123456"查询数据表中某个字段是否包含某个值1:模糊查询  使用like       select * ...

  8. React 组件通信方式

    人生的游戏不在于拿了一副好牌,而在于怎样去打好坏牌,世上没有常胜将军,勇于超越自我者才能得到最后的奖杯. 1. 父子组件通信方式 1.1 父组件传递到子组件 直接通过属性进行传递,数据的传递可以提高组 ...

  9. Python函数每日一讲 - 一文让你彻底掌握Python中的frozenset函数

    引言 在 Python 中,frozenset() 函数是一个重要的工具,用于创建不可变的集合对象.本文将介绍 frozenset() 函数的语法.用法示例以及实际应用场景,帮助大家更好地理解和应用这 ...

  10. HTTP1.0/HTTP1.1/HTTP2.0的演进

    HTTP1.0 短连接,每次请求都需要重新建立连接 不支持断点续传 HTTP1.1 支持长连接,同一个客户端连接可保持长连接,请求可在连接中顺序发出. 查看http请求头中有keepalive 参数 ...