2016-03-11 151 views
1

今天我读了一些关于Handler & Looper如何协同工作的博客和源代码。为什么主线程的Looper.loop()不会阻塞UI线程?

根据我所了解的情况,我们可以通过使用ThreadLocal魔法在每个线程上只有一个Looper。通常Handler是在主线程中启动的,否则您必须在单独的线程上手动启动或说出Looper,然后将其循环。

class LooperThread extends Thread { 
    public Handler mHandler; 

    public void run() { 
     Looper.prepare(); 

     mHandler = new Handler() { 
      public void handleMessage(Message msg) { 
       // process incoming messages here 
      } 
     }; 

     Looper.loop(); 
    } 
} 

真正困惑我的是主线程中的loop()。正如我在Looper的源代码中读到的那样。处理消息队列,然后为回调处理消息发送消息是一个无限循环。

根据这个https://stackoverflow.com/a/5193981/2290191,Handler和它的Looper运行在同一个线程中。

如果主线程上有一个无限循环,是不是会阻塞整个UI系统?

我知道我必须如此傻才能错过什么。但有人透露这背后的秘密是可爱的。

public static void loop() { 
    final Looper me = myLooper(); 
    if (me == null) { 
     throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 
    } 
    final MessageQueue queue = me.mQueue; 

    // Make sure the identity of this thread is that of the local process, 
    // and keep track of what that identity token actually is. 
    Binder.clearCallingIdentity(); 
    final long ident = Binder.clearCallingIdentity(); 

    for (;;) { 
     Message msg = queue.next(); // might block 
     if (msg == null) { 
      // No message indicates that the message queue is quitting. 
      return; 
     } 

     // This must be in a local variable, in case a UI event sets the logger 
     Printer logging = me.mLogging; 
     if (logging != null) { 
      logging.println(">>>>> Dispatching to " + msg.target + " " + 
        msg.callback + ": " + msg.what); 
     } 

     msg.target.dispatchMessage(msg); 

     if (logging != null) { 
      logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 
     } 

     // Make sure that during the course of dispatching the 
     // identity of the thread wasn't corrupted. 
     final long newIdent = Binder.clearCallingIdentity(); 
     if (ident != newIdent) { 
      Log.wtf(TAG, "Thread identity changed from 0x" 
        + Long.toHexString(ident) + " to 0x" 
        + Long.toHexString(newIdent) + " while dispatching to " 
        + msg.target.getClass().getName() + " " 
        + msg.callback + " what=" + msg.what); 
     } 

     msg.recycleUnchecked(); 
    } 
} 

回答

2

实际上,主线程中的Looper是允许绘制的。当一个视图无效时,一条消息被传递给主Looper,告诉它请求了一个平局。当Looper处理该消息时,会发生实际绘图。阻碍UI线程的其他活动支持绘图的原因是它阻止Looper处理该绘制消息。

这是或多或少的绘图工作在任何基于事件的系统,从Windows到Mac到Android。

为什么不立即绘制而不是发送消息?性能。绘图很慢。如果您针对某个事件做了多个更改,则不需要重新绘制每个屏幕。这样做意味着您将所有重绘处理单个事件合并为一次重绘。例如,如果您设置1视图的文本和另一个视图的图像,则它们都将同时重绘,并且只会重绘一次。

+0

我不认为你仔细阅读我的问题。我的意思是,如果主线程上有for(;;){}'循环,那么如何发送消息来绘制UI? –

+0

我做过了,我不认为你理解我的答案。其中一条消息引起了抽签。平局是发送给该活套的特殊信息。 –

+2

或者换一种说法 - 你认为主线程实际上是Looper处理消息并调用你的代码来响应它。这是一种称为事件循环或消息循环的技术,这在事件驱动编程中很常见。以下是Android的http://mattias.niklewski.com/2012/09/android_event_loop.html –

0

这个问题是一个微妙的陷阱。为什么无限循环不会阻塞UI线程,因为所有的UI线程行为都是从msg.next开始的。

如果没有消息,则表示不需要更新。我们所有的代码只是一个回调,比如Application onCreate,Activit onCreate,BroadcastReceiver onReceive。

所有更新回调都是由消息引起的,而这些消息来自系统服务,如ActivityManagerService, InputManagerService,WindowMangerService。如果你需要更新UI,android服务会通过IPC发送一条消息给循环。

所以无限循环是无限更新。