目录介绍

  • 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控件的访问加上锁机制的原因
    • 上锁会让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.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • 我的个人站点:www.yczbj.org, www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e

转载于:https://www.cnblogs.com/yc211/p/10254600.html

06.Android之消息机制问题相关推荐

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

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

  2. Android异步消息机制

    2019独角兽企业重金招聘Python工程师标准>>> 目录介绍 1.Handler的常见的使用方式 2.如何在子线程中定义Handler 3.主线程如何自动调用Looper.pre ...

  3. Android的消息机制(2)

    上一节中,是主线程自己发了一个消息到自己的Message Queue中,并把消息从队列中提取出来.那么如何由别的线程发送消息给主线程的Message Queue中呢? 直接看代码~~ 1 2 3 4 ...

  4. Android的消息机制

    Android的消息机制(一) android 有一种叫消息队列的说法,这里我们可以这样理解:假如一个隧道就是一个消息队列,那么里面的每一部汽车就是一个一个消息,这里我们先忽略掉超车等种种因素,只那么 ...

  5. 聊一聊Android的消息机制

    2019独角兽企业重金招聘Python工程师标准>>> 聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前 ...

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

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

  7. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  8. Android 开发艺术探索——第十章 Android的消息机制

    Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机 ...

  9. 【学习】Android的消息机制

    Android的消息机制主要指Handler的运行机制,而Handler的运行需要MessageQueue和Looper支撑 前置知识 MessageQueue:消息队列,内部以单链表的形式存储消息列 ...

  10. 【安卓学习笔记】Android Handler 消息机制探究

    一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...

最新文章

  1. Elasticsearch之数据建模
  2. linux日志生成速率统计,Linux学习29-awk提取log日志信息,统计日志里面ip访问次数排序...
  3. processing pushMartix
  4. java servlet 多线程_java – 多线程GAE servlet来处理并发用户
  5. PAT_B_1006_Java(15分)
  6. stat函数_数据分析工具入门 掌握这些Excel函数就够了
  7. MySQL的FROM_UNIXTIME()和UNIX_TIMESTAMP()函数的区别
  8. [转载] css border-collapse
  9. C#数组和集合专题2(Array)
  10. 未捕获异常string was not recognized_给你代码:PHP7中的异常与错误处理
  11. 血腥!实况转播SQL注入全过程,让你知道危害有多大。
  12. python中def demo是什么意思_Python def函数的定义、使用及参数传递实现代码
  13. [风一样的创作]二次封装阿里云短信 验证码 发送短信 查询短信 编辑短信
  14. com.101tec.zkclient使用
  15. 华东理工大学2022计算机考研,2022社会学考研经验贴
  16. pytorch中的register_parameter()和parameter()
  17. 根号6用计算机怎么算,根号6等于多少怎么算
  18. 基因表达半衰期 | mRNA Half-Life
  19. 一、2、JAVA--集合
  20. DLT645通信规约DTU 智能远程抄表

热门文章

  1. js验证银行卡号 luhn校验规则
  2. OpenCV探索之路(十四):绘制点、直线、几何图形
  3. Uva 1471 Defense Lines(LIS变形)
  4. Atitit 项目管理 提升开发效率的项目流程方法模型 哑铃型  橄榄型 直板型
  5. 这个教授的观点颇犀利
  6. java 开发小记:如何使用 MyEclipse 开发自己的类库(mylib.jar)以及引用(使用)她...
  7. iOS 常见的JS与iOS交互的需求与解决方案
  8. 知乎高赞:当update修改数据与原数据相同时会再次执行吗?
  9. 彻底搞懂“红黑树”......
  10. 碉堡了,独家首发Java核心知识点总结,超全!