Handle消息机制之一个问题引发的源码分析

我们都知道,在子线程中尝试进行UI操作或者在主线程中做耗时的操作(网络请求,大量数据库操作等),程序就有可能会报ANR或造成崩溃。

转载请注明出处:Vincent Blog‘s

为什么说是一个问题引发的对Handler源码的分析呢?先卖个关子这里先不说。我们先谈一谈Handler机制的通俗解释,如下:

  • Handler机制是处理不同线程间通信,那么我们是不是主要涉及到Handler类和Thread类

Thread类(实现了Runnable接口)

MessageQueue:我们应该可以猜出来是消息队列,存放线程存入的消息
Looper:管理线程的工具

Handler类

重写了handlerMessage方法,用于不同线程间通信,举个实际例子:
在实际开发中我们要从网络获取数据,对于网络请求数据是不可能放在主线程(程序启动时就会开启主线程也叫UI线程)中执行,
请求数据是耗时操作,如果放在主线程会造成程序ANR,一般我们是怎样处理,开启一条子线程,
把一切耗时的操作放在子线程执行,让UI线程(从字面就可看出是专门管理UI界面的)去更新UI,
但是问题来了,当我们网络数据请回来后,UI线程就怎么知道去将数据显示在界面呢?

首先在主线中创建子线程,让子线程去完成网络请求这一耗时操作,在此之前需要在主线程创建Handler对象

     Handler mHandler=new Handler(){
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
       }
   };

接下来说一下怎样将数据通过主线程更新显示在界面。

  • 子线程将自己请求回来的消息放进自己的MessageQueue(消息队列)
  • 子线程通过Looper将MessageQueue里面的数据取出
  • 子线程通过Looper将数据发送给Handler
  • 主线程通过Looper将Handler中的数据取出
  • 主线程通过Looper将取出的的数据发送给主线程的MessageQueue
  • 最后主线程执行对消息处理的任务

我相信,大家对Handler机制有一定的了解了,Handler机制在面试中也是经常问的一道题。OK,接下来就要引入标题的问题了,这个问题还是我同学问我的,Can't create handler inside thread that has not called Looper.prepare()报这个错问我怎样解决,我当时看了一下他的代码(很高冷的-)对他说需要在子线程调一下Looper.prepare(),之后在创建Handler对象,但是我们就仅限于知道怎样解决问题吗,显然这不符合我的风格,要想弄清楚原理我们只能从源码着手,OK,我们来看一下Handler的源码,为什么不调用Looper.prepare()就不行呢

Handler无参构造函数

Handler.png

  看红色矩形框内Looper.myLooper()方法获取了一个对象,做了一个判断如果mLooper等于空时抛了一个运行时异常,该错误就是Can't create handler inside thread that has not called Looper.prepare(),那么mLooper对象什么时候为空呢?我们需要看一下Looper.myLooper()方法中的代码:

mylooper.png

只有一行,sThreadLocal取出的是Looper,如果Looper存在,那么就直接返回Looper,如果不存在那就直接返回null,我们再接着看Looper.prepare()方法中的代码:

looperprepare.png

不带参的prepare方法中调用的还是带参的prepare方法,我们直接看带参的这个,首先做了一个判断,sThreadLocal是否存在Looper,存在抛出一个运行时异常,如果不存在就直接创建一个新的Looper,这里应该知道为什么要先调用Looper.prepare()方法了吧,只有存在Looper后才能new Handler对象,并且每一个线程只能对应一个Looper。可能有的哥们会问,我在主线程也没有调用Looper.prepare()啊,为什么没有报错呢?这是因为主线程默认就存在一个Looper并不需要我们手动创建。我们看一下主线程ActivityThread类的main方法:

activitymain.png

我们看一下红色框内调用了Looper.prepareMainLooper()方法,其实看一下prepareMainLooper()里面的代码我们就会知道调用的还是Looper.prepare()

prepareMainLooper.png

这次是不是很清楚了,也就是主线程可以直接创建Handler对象,子线程需要先调用Looper.prepare();在创建Handler对象。

在上面我们已经通俗的讲解了Handler机制,现在我们在进一步来了解Handler,在Handler中有好几种发送消息的方法,我们主要看一下sendMessageAtTime(Message msg,long uptimeMillis)方法

sendMessage.png

msg就是要发送的消息,uptimeMillis是发送消息的时间,然后将这两个参数传入MessageQueue的enqueueMessage方法中,MessageQueue我们在上面已经说了就是消息队列,对消息进行排列,并且有入队出队的方法,MessageQueue通过Looper的构造函数创建的,一个Looper对应一个MessageQueue,而enqueueMessage我们应该能猜出就是入队方法,我们来看一看它的源码:

enqueueMessage.png

我们要知道,消息在MessageQueue中并不是用集合或者其他保存起来的,而是使用mMessages对象表示当前待处理的消息,从红色框内可以看出,所谓入队其实就是将消息按照发送消息时的时间进行排序,这里的时间就是上面说的uptimeMillis,通过时间顺序调用msg.next确定下一条消息是什么。看完入队,我们看一下出队,我们来看一看Looper.loop()方法:

loop.png

你们看出来了什么,next()是不是就是出队的方法,你们有兴趣可以去MessageQueue中看看next()方法源码,我这里就不贴出来了,要不然一说停不下收不了尾。这里简单说一说消息出队的原理,如果MessageQueue存在mMessages待处理消息,那么Looper就取出这条消息,然后下一条就成为mMessages待处理消息,就这里一直循环,如果MessageQueue不存在mMessages待处理消息,那么就成为阻塞状态,直到下一条消息入队。看第二条红色线我们发现出队的消息都经过msg.target.dispatchMessage()进行处理,心细的同学可能知道msg.target其实就是Handler,不知道的同学返回去看sendMessageAtTime方法的源码第六行,接下来我们接着看Handler的dispatchMessage()

dispatchMessage.png

看了之后是不是感觉豁然开朗,我们经过一系列的发送消息、入队、出队、接收消息等,到最后调用Handler的handlerMessage方法,这时handlerMessage方法已经在主线程运行,就可以更新UI了。

呼,真心累,就到这里吧,我们下面文章见-

<b><i>联系方式</i>:lijiandongv@163.com</b> 有什么问题或者建议欢迎留言到我的邮箱

每日一碗鸡汤

<b>每当你想放弃的时候,就想想那一刻你的勇气。让成为过去的那一刻的勇气,变成此时此刻,你坚持的勇气。</b>

3.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容