从源码的角度分析Handler机

来源:互联网 发布:java 键值对会覆盖 编辑:程序博客网 时间:2024/05/20 09:47
众所周知Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。
这种处理方式被称为异步消息处理线程,可作为一个程序员,我们不能仅仅满足于会使用,更要做到知其然知其所以然。今天我们就来一起深入探究一下Handler和Message背后的秘密。(参考了第二行代码的作者,郭神的文章。写出来纯粹是为了笔记)
首先来看一下如何创建Handler对象。你可能会觉得挺纳闷的,创建Handler有什么好看的呢,直接new一下不就行了?确实,不过即使只是简单new一下,还是有不少地方需要注意的,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:
public class MainActivity extends Activity {            private Handler handler1;            private Handler handler2;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          handler1 = new Handler();          new Thread(new Runnable() {              @Override              public void run() {                  handler2 = new Handler();              }          }).start();      }    }

如果现在运行一下程序,你会发现,在子线程中创建的Handler是会导致程序崩溃的,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() 。说是不能在没有调用Looper.prepare() 的线程中创建Handler,报错的位置是在handler2的构造函数中,那么我们查看一下,handler中的无参构造函数
    public Handler() {        this(null, false);    }
它通过this调用了有两个参数的构造函数,传递的两个参数分别是null和false:
    public Handler(Callback callback, boolean async) {        if (FIND_POTENTIAL_LEAKS) {            final Class<? extends Handler> klass = getClass();            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&                    (klass.getModifiers() & Modifier.STATIC) == 0) {                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +                    klass.getCanonicalName());            }        }        mLooper = Looper.myLooper();        if (mLooper == null) {            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }        mQueue = mLooper.mQueue;        mCallback = callback;        mAsynchronous = async;    }
小伙伴们,我们来看看第十二行,他会对looper进行判断,若looper为空的话就会抛出异常,也就是说Looper.myLooper()方法返回的looper对象为空,那我们再来看看lopper的myLopper()方法:

    /**     * Return the Looper object associated with the current thread.  Returns     * null if the calling thread is not associated with a Looper.     */    public static @Nullable Looper myLooper() {        return sThreadLocal.get();    }
官方给的注释已经很清了,也就是说返回和当前线程所关联的Looper对象,若没有的话会返回空。我们再来看看刚才给我们报的错误:Can't create handler inside thread that has not called Looper.prepare(),也就是说, Looper.prepare()此方法会帮我们返回一个looper对象:
    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));    }
可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
你可能会比较疑惑。主线程中的Handler也没有调用Looper.prepare()方法,为什么就可以正常使用呢?细心的朋友我相信都已经发现了这一点,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:
public static void main(String[] args) {      SamplingProfilerIntegration.start();      CloseGuard.setEnabled(false);      Environment.initForCurrentUser();      EventLogger.setReporter(new EventLoggingReporter());      Process.setArgV0("<pre-initialized>");      Looper.prepareMainLooper();      ActivityThread thread = new ActivityThread();      thread.attach(false);      if (sMainThreadHandler == null) {          sMainThreadHandler = thread.getHandler();      }      AsyncTask.init();      if (false) {          Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));      }      Looper.loop();      throw new RuntimeException("Main thread loop unexpectedly exited");  }  
在第7行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:
public static final void prepareMainLooper() {      prepare();      setMainLooper(myLooper());      if (Process.supportsProcesses()) {          myLooper().mQueue.mQuitAllowed = false;      }  }
因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。
总结一下:new handler之前必须要有looper对象,在UI线程中系统帮我们调用的looper.prepare方法产生了一个looper对象,而我们自己的线程中需要new handler的话,就必须先调用looper对象prepare方法产生一个looper对象,那么可能有小伙伴会疑惑为什么不new 一个呢?相信大家都明白,必然是构造方法私有了。
    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }
只能通过prepare()方法来获取looper对象;
看完了如何创建Handler之后,可能有不少小伙伴比较疑惑,为什么要先有有looper呢?还是通过源码,来解决你的疑惑:
    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }
在looper的构造方法中会有帮我们造一个messageQueue,也就是消息队列。此消息队列是handler通过send或者post发送消息的目的地,试问若容器都不存在,如何装东西呢?这里做一个比喻,将messageQueue比作邮箱,把Hanlder比作邮递员,既然邮箱和邮递员都存在了,那么现在是不是就缺少邮件呢?也就是信。信在Android中也就是Message对象,这里为了节省信封,我们一般使用Message对象的obtain方法来循环利用信封,它的内部是是使用链表结构存储多个消息对象,最大长度为50,目的是为了重复的使用Message对象,避免每次都创建消息对象,避免造成大量的垃圾对象:
   private static int sPoolSize = 0;   private static final int MAX_POOL_SIZE = 50;
    public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }
取得messenge对象之后,可以调用 setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了
new Thread(new Runnable() {      @Override      public void run() {          Message message = new Message();          message.arg1 = 1;          Bundle bundle = new Bundle();          bundle.putString("data", "data");          message.setData(bundle);          handler.sendMessage(message);      }  }).start(); 
可是这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了,Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,毕竟殊途同归麽。这个方法的源码如下所示:
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;        if (queue == null) {            RuntimeException e = new RuntimeException(                    this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);            return false;        }        return enqueueMessage(queue, msg, uptimeMillis);    }
sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。
那么我们来看一看发送时的入队方法:
final boolean enqueueMessage(Message msg, long when) {      if (msg.when != 0) {          throw new AndroidRuntimeException(msg + " This message is already in use.");      }      if (msg.target == null && !mQuitAllowed) {          throw new RuntimeException("Main thread not allowed to quit");      }      synchronized (this) {          if (mQuiting) {              RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");              Log.w("MessageQueue", e.getMessage(), e);              return false;          } else if (msg.target == null) {              mQuiting = true;          }          msg.when = when;          Message p = mMessages;          if (p == null || when == 0 || when < p.when) {              msg.next = p;              mMessages = msg;              this.notify();          } else {              Message prev = null;              while (p != null && p.when <= when) {                  prev = p;                  p = p.next;              }              msg.next = prev.next;              prev.next = msg;              this.notify();          }      }      return true;  }  
首先,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper的另一个重要的作用了。Looper.loop()方法的源码了,如下所示:
  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            final Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            final long traceTag = me.mTraceTag;            if (traceTag != 0) {                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));            }            try {                msg.target.dispatchMessage(msg);            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            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();        }    }
可以看到,这个方法从for语句开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法中的 msg.target.dispatchMessage(msg)方法,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:
public void dispatchMessage(Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }
在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:



(注:看了那么多图,还是觉得郭神的图最好,就盗过来用了)
另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:
还可以调用Handler的post()系列的方法:
post方法的源码如下:
    public final boolean post(Runnable r)    {       return  sendMessageDelayed(getPostMessage(r), 0);    }
这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:
    private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }
在这个方法中将消息的callback字段的值指定为传入的Runnable对象。这个callback字段不就是handler方法的dispatchMessage()方法中的那个callback吗?在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。handleCallback()代码如下:
    private static void handleCallback(Message message) {        message.callback.run();    }
也就是直接调用了一开始传入的Runnable对象的run()方法。虽然写法上相差很多,但是原理是完全一样的,我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。
0 0