Handler,MessageQueue,Runnable与Looper(下)

前言

前篇对MessageQueue、Handler等几个概念进行了概述,相信大家一定也有了一定的理解。接下来将对上篇遗留的问题进行研究。由于上面中LooperThread例子只是一个壳,没有可真正运行的”内容“。所以要回答剩余的问题,ActivityThread是一个很好的示例。从名称上看ActivityThread就是我们所熟悉的主线程。

示例

public static void main(String[] args) {
         ...
         Looper.prepareMainLooper() ;
         ActivityThread thread = new ActivityThread();
         thread.attach(false);
         if ( sMainThreadHandler == null){
                 sMainThreadHandler = thread.getHandler();
         }
         AsyncTask.init();
         Looper,loop();
         throw new RuntimeException("Main thread loop unexpectedly exited");
}

如果比较上面这段代码与LooperThread.run()的实现,就可以发现它们在整体架构上是一样的,区别主要体现在:

  • prepareMainLooper和prepare
    普通线程只要prepare就可以了;而主线程使用的是prepareMainLooper。
  • Handler 不同
    普通线程生成一个与Looper绑定的Handler对象的就行;而主线程是从当前当前线程中获得的Handler(thread.getHandler());
  1. 那么,prepareMainLooper有什么特殊之处?
public static void prepareMainLooper() {
          prepare (false); //先调用prepare
          synchronized (Looper.class) {
                if (sMainLooper != null) {
                       throw new IllegalStateException("The main Looper has already been prepared.");
               }
               sMainLooper = myLooper ();
           }
}

我们可以看到,在prepareMainLooper也是需要用到prepare.参数false表示该线程不允许退出,这和前面的LooperThread不一样,经过prepare后,myLooper就可以得到一个本地线程<ThreadLocal>的Looper对象,然后将其赋给sMainLooper。从这个角度来讲,主线程的sMainLooper其实和其他线程的Looper对象并没有本质的区别。


Looper揭秘.png

这个图描述的是一个进程和它内部两个线程的Looper情况,其中线程1是主线程,线程2是普通线程。方框表示它们能访问的范围,如线程1就不能直接访问到线程2中的Looper对象,但二者都可以接触到进程中的各元素。
线程1:因为是Main Thread,它使用的是prepareMainLooper(),这个函数将通过prepare()为线程1生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样做的目的就是其他线程如果要获得主线程的Looper,只需调用getMainLooper()即可。
线程2:作为普通线程,它调用的是prepare();同时也生成一个ThreadLocal的Looper对象,只不过这个对象只能在线程内通过myLooper()访问。当然,主线程内部也可以通过这个函数访问它的Looper对象。
由此可见,Google玩了一个技巧,从而巧妙的区分开各线程的Looper,并界定了它们的访问权限。

  1. sMainThreadHandler。当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象:
    final H mH = new H();
    ActivityThread.main中调用的tread.getHandler()返回的就是mH。
    也就是说,ActivityThread提供了一个“事件管家”,以处理主线程中的各种消息。
    接下来我们分析下loop()函数。
public static void loop() {
         final Looper me = myLooper () ;
         /*loop函数也是静态的,所以它只能访问静态的数据。函数myLooper则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实就是取出之前prepare中创建那个Looper对象)*/
         ...
         final MessageQueue queue = me.mQueue;
         /*正如我们所说,Looper中自带一个MessageQueue*/
         for( ; ; ){//消息循环开始
                 Message msg = queue.next();/*从MessageQueue中取出一个消息,可能会阻塞*/
                 if(msg == null){
                  /*如果当前消息队列中没有msg,说明线程要退出了。类比于上面Windows伪代码
                  例子中while判断条件为0,这样就会结束循环*/
           return;/*消息处理完毕,进行回收*/
                  }
          ...
          msg.target.dispatchMessage(msg);
                  /*终于开始分派消息,重心就在这里。变量target其实是一个Handler,所以
                  dispatchMessage最终调用的是Handler中的处理函数*/
          ...
                  msg.recycle () ;/*消息处理完毕,进行回收*/
         }
}

可以看到,loop()函数的主要工作就是不断地从消息队列中取出需要处理的事件,然后分发给相应的负责人。如果消息队列为空,它很可能会进入睡眠以让出cpu资源。而在具体事件的处理过程中,程序会post新的事件到队列中。另外,其他进程也可能投递新的事件到这个队列中。APK应用程序就会不停地执行“处理队列事件”的工作,直到它退出运行。
以上我们看到了Looper.loop的处理流程,从而知道它和前面讨论的Windows消息处理机制是类似的,最后再来解决一个问题:MessageQueue是怎样创建出来的?
我们有提到过,Looper中带有唯一一个MessageQueue,是不是这样?

/*
以下代码还是Looper.java中的,不过只提取出MessageQueue相关的部分
*/
final MessageQueue mQueue ; /*注意它不是static的*/
private Looper(boolean quitAllowed) {
     mQueue = new MessageQueue( quitAllowed );
     /*new了一个MessageQueue,就是它了。也就是说,当Looper创建时,消息队列也同时会被创建出来*/
     mRun = true;
     mThread = Thread.currentThread();//Looper与当前线程建立对应关系
     }

事实证明Looper内部的确管理了一个MessageQueue,它将作为线程的消息存储仓库,配合Handler,Looper一起完成一系列操作。

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

推荐阅读更多精彩内容