Handler,MessageQueue,Runnable与Lo

2018-07-25  本文已影响28人  小庄bb

前言

前篇对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()的实现,就可以发现它们在整体架构上是一样的,区别主要体现在:

  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一起完成一系列操作。

上一篇 下一篇

猜你喜欢

热点阅读