从Handler+Message+Looper源码带你分析Android系统的消息处理机制

来源:互联网 发布:期货配资软件 编辑:程序博客网 时间:2024/05/22 03:32

作为Android开发者,相信很多人都使用过Android的Handler类来处理异步任务。那么Handler类是怎么构成一个异步任务处理机制的呢?这篇

博客带你从源码分析Android的消息循环处理机制,便于深入的理解。

这里不得不从“一个Bug引发的思考”开始研究Android的消息循环处理机制。说来话长,在某一次的项目中,原本打算开启一个工作线程

WorkThread去执行一个耗时任务,然后在工作线程WorkThread中new一个Handler对象来发送消息。代码简化成如下:

<code class="hljs java has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WorkThread</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Thread</span> {</span>        <span class="hljs-keyword">private</span> Handler mHandler;        <span class="hljs-annotation">@Override</span>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {            mHandler = <span class="hljs-keyword">new</span> Handler() {                <span class="hljs-annotation">@Override</span>                <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {                    <span class="hljs-keyword">switch</span> (msg.what) {                        <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:                            Log.e(TAG, 任务执行完成);                            <span class="hljs-keyword">break</span>;                    }                }            };            <span class="hljs-comment">//模拟一个耗时任务</span>            <span class="hljs-keyword">try</span> {                sleep(<span class="hljs-number">9000</span>);            } <span class="hljs-keyword">catch</span> (InterruptedException e) {                e.printStackTrace();            }            <span class="hljs-comment">//任务执行完成之后发送一个消息</span>            <span class="hljs-keyword">int</span> what = <span class="hljs-number">0</span>;            mHandler.sendEmptyMessage(what);        }    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul>

当以上代码执行之后,很不幸的出现了如下的一个Bug:

这里写图片描述

Log打印日志提示:不能在线程中没有调用 Looper.prepare()方法之前就去创建handler对象,言外之意就是,在线程中还没调Looper.prepare()

方法之前,你是不能去创建Handler对象的,否则抛出错误异常。为什么会这样呢?带着疑问,我们跟踪代码进入到Handler源码中的构造方法:

<code class="hljs lasso has-numbering"><span class="hljs-keyword">public</span> Handler(Callback callback, boolean async) {        <span class="hljs-attribute">...</span><span class="hljs-attribute">...</span><span class="hljs-attribute">...</span><span class="hljs-attribute">...</span><span class="hljs-built_in">.</span>        mLooper <span class="hljs-subst">=</span> Looper<span class="hljs-built_in">.</span>myLooper();        <span class="hljs-keyword">if</span> (mLooper <span class="hljs-subst">==</span> <span class="hljs-built_in">null</span>) {            throw <span class="hljs-literal">new</span> RuntimeException(                Can<span class="hljs-string">'t create handler inside thread that has not called Looper.prepare());        }        mQueue = mLooper.mQueue;        mCallback = callback;        mAsynchronous = async;    }</span></code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

代码第5-8行:我们发现当成员变量mLooper为空值时,就会抛出上面的异常了,意思就是刚才在WorkThread中创建Handler的时候mLooper是

空的,难道不可以这样创建Handler消息处理机制?那为什么在UI线程中直接new一个Hanlder对象不会出错呢?带着这种好奇心,我们今天来分

析一下Android系统消息处理机制有关的 Handler,Message,Looper,Thread类之间的关联。

  • Handler:消息的执行者,也可以称之为异步任务的执行者
  • Message:消息的封装者,把异步任务,消息码Handler对象等封装成Message对象
  • MessageQueue:消息队列,用于保存当前线程的所有消息Message对象的一个列表
  • Looper:循环者,能让工作线程变成循环线程,然后从消息队列中循环读取消息
  • Thread:异步任务或者耗时任务执行场所,一般开启一个新的工作线程处理耗时任务

消息的执行者–Handler

在Android的消息处理机制中,Handler扮演者重要的角色。Handler负责如下几个工作:

消息的发送 消息的入列 消息的调度/消息的分发 消息的处理

1-1消息的发送

对于消息的发送,相信很多人平时用的最多的是Handler.sendEmptyMessage(),那么利用Handler发送的消息最终会发送到哪里呢?骚年不用YY了,源码会告诉你答案,我们跟踪Handler类中的sendEmptyMessage方法:

<code class="hljs java has-numbering"><span class="hljs-javadoc">/**     * Sends a Message containing only the what value.     *       *<span class="hljs-javadoctag"> @return</span> Returns true if the message was successfully placed in to the      *         message queue.  Returns false on failure, usually because the     *         looper processing the message queue is exiting.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">sendEmptyMessage</span>(<span class="hljs-keyword">int</span> what)    {        <span class="hljs-keyword">return</span> sendEmptyMessageDelayed(what, <span class="hljs-number">0</span>);    }<span class="hljs-javadoc">/**     * Sends a Message containing only the what value, to be delivered     * after the specified amount of time elapses.     *<span class="hljs-javadoctag"> @see</span> #sendMessageDelayed(android.os.Message, long)      *      *<span class="hljs-javadoctag"> @return</span> Returns true if the message was successfully placed in to the      *         message queue.  Returns false on failure, usually because the     *         looper processing the message queue is exiting.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">sendEmptyMessageDelayed</span>(<span class="hljs-keyword">int</span> what, <span class="hljs-keyword">long</span> delayMillis) {        Message msg = Message.obtain();        msg.what = what;        <span class="hljs-keyword">return</span> sendMessageDelayed(msg, delayMillis);    }<span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">sendMessageDelayed</span>(Message msg, <span class="hljs-keyword">long</span> delayMillis)    {        <span class="hljs-keyword">if</span> (delayMillis < <span class="hljs-number">0</span>) {            delayMillis = <span class="hljs-number">0</span>;        }        <span class="hljs-keyword">return</span> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }<span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">sendMessageAtTime</span>(Message msg, <span class="hljs-keyword">long</span> uptimeMillis) {        MessageQueue queue = mQueue;        <span class="hljs-keyword">if</span> (queue == <span class="hljs-keyword">null</span>) {            RuntimeException e = <span class="hljs-keyword">new</span> RuntimeException(                    <span class="hljs-keyword">this</span> +  sendMessageAtTime() called with no mQueue);            Log.w(Looper, e.getMessage(), e);            <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;        }        <span class="hljs-keyword">return</span> enqueueMessage(queue, msg, uptimeMillis);    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li></ul>

有以上代码发现,Handler消息的发送最终都会调用sendMessageAtTime成员方法。该方法首先判断当前Handler的成员变量mQueue是否为空,如果为空,则打印一个警告,并且返回false,表

示该消息发送失败。那么成员变量mQueue是神马东西呢?mQueue是MessageQueue对象,而MessageQueue类是一个消息队列,被Looper

对象持有。关于MessageQueue消息队列相关内容后面会展开分析。继续分析代码,如果消息队列不为空,则会调用enqueueMessage方法,跟踪代码进入该方法:

<code class="hljs java has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">enqueueMessage</span>(MessageQueue queue, Message msg, <span class="hljs-keyword">long</span> uptimeMillis) {        msg.target = <span class="hljs-keyword">this</span>;        <span class="hljs-keyword">if</span> (mAsynchronous) {            msg.setAsynchronous(<span class="hljs-keyword">true</span>);        }        <span class="hljs-keyword">return</span> queue.enqueueMessage(msg, uptimeMillis);    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>

首先,将当前类Handler对象赋值给消息Message类中的target成员变量。然后调用消息队列MessageQueue类中的enqueueMessage方法将

该消息msg插入到消息队列中。这个过程称之为消息入列的一个过程。

1-2消息的入列

有1-1节可知,消息的入列是调用MessageQueue消息队列类中的enqueueMessage成员方法实现的,跟踪代码看看消息入列的具体实现

<code class="hljs fsharp has-numbering"> boolean enqueueMessage(Message msg, long <span class="hljs-keyword">when</span>) {        <span class="hljs-comment">//判断消息的目标对象是否为空</span>        <span class="hljs-keyword">if</span> (msg.target == <span class="hljs-keyword">null</span>) {            throw <span class="hljs-keyword">new</span> IllegalArgumentException(Message must have a target.);        }        <span class="hljs-comment">//判断消息是否正在使用</span>        <span class="hljs-keyword">if</span> (msg.isInUse()) {            throw <span class="hljs-keyword">new</span> IllegalStateException(msg +  This message is already <span class="hljs-keyword">in</span> <span class="hljs-keyword">use</span>.);        }        synchronized (this) {            <span class="hljs-comment">//判断是否正在清除消息队列</span>            <span class="hljs-keyword">if</span> (mQuitting) {                IllegalStateException e = <span class="hljs-keyword">new</span> IllegalStateException(                        msg.target +  sending message <span class="hljs-keyword">to</span> a Handler on a dead thread);                Log.w(MessageQueue, e.getMessage(), e);                msg.recycle();                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;            }            <span class="hljs-comment">//标记消息正在使用</span>            msg.markInUse();            <span class="hljs-comment">//对消息的延时处理时间幅值</span>            msg.<span class="hljs-keyword">when</span> = <span class="hljs-keyword">when</span>;            <span class="hljs-comment">//保存消息队列中的延时时间最小的那个消息</span>            Message p = mMessages;            boolean needWake;            <span class="hljs-comment">//条件判断,消息入列,将延时时间最小的那个消息赋值给mEssages变量</span>            <span class="hljs-keyword">if</span> (p == <span class="hljs-keyword">null</span> || <span class="hljs-keyword">when</span> == <span class="hljs-number">0</span> || <span class="hljs-keyword">when</span> < p.<span class="hljs-keyword">when</span>) {                <span class="hljs-comment">// New head, wake up the event queue if blocked.</span>                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } <span class="hljs-keyword">else</span> {                <span class="hljs-comment">// Inserted within the middle of the queue.  Usually we don't have to wake</span>                <span class="hljs-comment">// up the event queue unless there is a barrier at the head of the queue</span>                <span class="hljs-comment">// and the message is the earliest asynchronous message in the queue.</span>                needWake = mBlocked && p.target == <span class="hljs-keyword">null</span> && msg.isAsynchronous();                Message prev;                <span class="hljs-comment">//循环遍历消息队列,将当前消息按照延时时间插入到消息队列中</span>                <span class="hljs-keyword">for</span> (;;) {                    prev = p;                    p = p.next;                    <span class="hljs-keyword">if</span> (p == <span class="hljs-keyword">null</span> || <span class="hljs-keyword">when</span> < p.<span class="hljs-keyword">when</span>) {                        break;                    }                    <span class="hljs-keyword">if</span> (needWake && p.isAsynchronous()) {                        needWake = <span class="hljs-keyword">false</span>;                    }                }                msg.next = p; <span class="hljs-comment">// invariant: p == prev.next</span>                prev.next = msg;            }            <span class="hljs-comment">// We can assume mPtr != 0 because mQuitting is false.</span>            <span class="hljs-keyword">if</span> (needWake) {                nativeWake(mPtr);            }        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li></ul>

分析:
1.代码第3行:判断当前入列的消息的目标处理对象是否为空,有1-1节我们知道msg.target的值是一个Handler实例。换言之,如果消息的目标处

理对象Handler为null,那么该消息就没有Handler去处理它,因此此处抛出一个异常

2.代码第7行:判断当前Message是否正在使用?如果是则抛出异常。说明一个相同的消息不可能同时存在同一个MessageQueue消息队列中。

3.代码第13行:判断当前消息队列是否正在退出?如果正在退出,则抛出异常,说明当前Message消息无法入列,因为MessageQueue消息队列退出了。

4.代码第27-52行:Message消息按照延时时间大小插入当前MessageQueue消息队列中,最终延时时间最短的消息在队列的最前面。

总结:到此,我们知道Handler将消息发送到消息队列MessageQueue中去了。那么这个消息队列MessageQueue是从哪里来

的呢?它属于谁呢?这里直接给出答案,MessageQueue消息队列是在Looper类中创建,Looper循环则持有当前线程中的消息队列

MessageQueue。至于为什么是这样,后面给出详细解释。

1-3消息的调度/消息的分发

有1-2小节我们知道,消息的执行者Handler将消息Message发送到消息队列MessageQueue中,并且消息队列中的所有消息都是按照时间排列。

那么在消息队列MessageQueue中的消息又是怎么分发出去的,或者说消息队列中的消息是怎么被消费掉的?现在我们来解答1-2小节中的最后一个问题,消息队列MessageQueue从哪里来?属于谁?在文章的一开头,

我们有“一个Bug引发的思考”知道了在线程中创建Handler对象之前需要调用Looper.prepare(),否则会抛出异常。那么我们就来分析一下Looper这个类:

<code class="hljs coffeescript has-numbering">/**  * Class used to run a message <span class="hljs-keyword">loop</span> <span class="hljs-keyword">for</span> a thread.  Threads <span class="hljs-keyword">by</span> <span class="hljs-reserved">default</span> <span class="hljs-keyword">do</span>  * <span class="hljs-keyword">not</span> have a message <span class="hljs-keyword">loop</span> associated <span class="hljs-reserved">with</span> them; to create one, call  * {<span class="hljs-property">@link</span> <span class="hljs-comment">#prepare} in the thread that is to run the loop, and then</span>  * {<span class="hljs-property">@link</span> <span class="hljs-comment">#loop} to have it process messages until the loop is stopped.</span>  *   * </code>Most interaction <span class="hljs-reserved">with</span> a message <span class="hljs-keyword">loop</span> <span class="hljs-keyword">is</span> through the * {<span class="hljs-property">@link</span> Handler} <span class="hljs-class"><span class="hljs-keyword">class</span>. * *</span>This <span class="hljs-keyword">is</span> a typical example <span class="hljs-keyword">of</span> the implementation <span class="hljs-keyword">of</span> a Looper thread, * using the separation <span class="hljs-keyword">of</span> {<span class="hljs-property">@link</span> <span class="hljs-comment">#prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. * *</span>  *  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LooperThread</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Thread</span> {</span>  *      public Handler mHandler;  *  *      public <span class="hljs-reserved">void</span> run() {  *          Looper.prepare();  *  *          mHandler = <span class="hljs-keyword">new</span> Handler() {  *              public <span class="hljs-reserved">void</span> handleMessage(Message msg) {  *                  <span class="hljs-regexp">//</span> process incoming messages here  *              }  *          };  *  *          Looper.<span class="hljs-keyword">loop</span>();  *      }  *  }*/</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul>

分析:源码开头给出了一个很长的说明,大意是:Looper类作用是为一个线程构造消息循环的,因为线程Thread默认是不带消息循环的。因此

你可以调用Looper类中的prepare方法去为构造一个带消息循环的线程,并且调用Looper.loop()方法启动循环去循环处理消息,直到loop循环结

束。并且Google官方还提供了一个标准的构造一个带消息循环的线程实例,代码如下:

<code class="hljs axapta has-numbering"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LooperThread</span> <span class="hljs-inheritance"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">Thread</span> {</span>       <span class="hljs-keyword">public</span> Handler mHandler;      <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> run() {         Looper.prepare();          mHandler = <span class="hljs-keyword">new</span> Handler() {              <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> handleMessage(Message msg) {                  <span class="hljs-comment">// process incoming messages here</span>              }         };          Looper.loop();      } }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

以上代码就是创建带消息循环的线程标准写法。那么我们来看看Looper.prepare()方法做了什么:

<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Looper</span> {</span>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TAG = Looper;    <span class="hljs-comment">// sThreadLocal.get() will return null unless you've called prepare().</span>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> ThreadLocal<looper> sThreadLocal = <span class="hljs-keyword">new</span> ThreadLocal<looper>();    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Looper sMainLooper;  <span class="hljs-comment">// guarded by Looper.class</span>    <span class="hljs-keyword">final</span> MessageQueue mQueue;    <span class="hljs-keyword">final</span> Thread mThread;    <span class="hljs-keyword">private</span> Printer mLogging;     <span class="hljs-javadoc">/** Initialize the current thread as a looper.      * This gives you a chance to create handlers that then reference      * this looper, before actually starting the loop. Be sure to call      * {@link #loop()} after calling this method, and end it by calling      * {@link #quit()}.      */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">prepare</span>() {        prepare(<span class="hljs-keyword">true</span>);    }    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">prepare</span>(<span class="hljs-keyword">boolean</span> quitAllowed) {        <span class="hljs-keyword">if</span> (sThreadLocal.get() != <span class="hljs-keyword">null</span>) {            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(Only one Looper may be created per thread);        }        sThreadLocal.set(<span class="hljs-keyword">new</span> Looper(quitAllowed));    }}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul>

分析:
1.代码第24行:调用sThreadLocal.get()获得存储在本地线程中的Looper值,如果本地线程ThreadLocal中有当前的Looper对象,者会抛出一个异

常:”Only one Looper may be created per thread”每一个线程只能有一个Looper对象。所以每一个线程只能拥有一个Looper对象,关于ThreadLocal类,这里不展开学习,主要作用是将Looper对象存储在本地线程ThreadLocal中。

2.代码第27行:将当前的Looper对象保存在本地线程对象ThreadLocal中。至此,Looper的准备工作就完成了。

Looper的准备工作完成之后,我们来看看Looper的启动方法Looper.loop(),进入loop()方法代码如下:

<code class="hljs fsharp has-numbering"> /**     * Run the message queue <span class="hljs-keyword">in</span> this thread. Be sure <span class="hljs-keyword">to</span> call     * {@link #quit()} <span class="hljs-keyword">to</span> <span class="hljs-keyword">end</span> the loop.     */    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> loop() {        final Looper me = myLooper();        <span class="hljs-keyword">if</span> (me == <span class="hljs-keyword">null</span>) {            throw <span class="hljs-keyword">new</span> RuntimeException(No Looper; Looper.prepare() wasn't called on this thread.);        }        final MessageQueue queue = me.mQueue;        <span class="hljs-comment">// Make sure the identity of this thread is that of the local process,</span>        <span class="hljs-comment">// and keep track of what that identity token actually is.</span>        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        <span class="hljs-keyword">for</span> (;;) {            Message msg = queue.next(); <span class="hljs-comment">// might block</span>            <span class="hljs-keyword">if</span> (msg == <span class="hljs-keyword">null</span>) {                <span class="hljs-comment">// No message indicates that the message queue is quitting.</span>                <span class="hljs-keyword">return</span>;            }            <span class="hljs-comment">// This must be in a local variable, in case a UI event sets the logger</span>            Printer logging = me.mLogging;            <span class="hljs-keyword">if</span> (logging != <span class="hljs-keyword">null</span>) {                logging.println(>>>>> Dispatching <span class="hljs-keyword">to</span>  + msg.target +   +                        msg.callback + :  + msg.what);            }            msg.target.dispatchMessage(msg);            <span class="hljs-keyword">if</span> (logging != <span class="hljs-keyword">null</span>) {                logging.println(<<<<< Finished <span class="hljs-keyword">to</span>  + msg.target +   + msg.callback);            }            <span class="hljs-comment">// Make sure that during the course of dispatching the</span>            <span class="hljs-comment">// identity of the thread wasn't corrupted.</span>            final long newIdent = Binder.clearCallingIdentity();            <span class="hljs-keyword">if</span> (ident != newIdent) {                Log.wtf(TAG, Thread identity changed from <span class="hljs-number">0</span>x                        + Long.toHexString(ident) +  <span class="hljs-keyword">to</span> <span class="hljs-number">0</span>x                        + Long.toHexString(newIdent) +  <span class="hljs-keyword">while</span> dispatching <span class="hljs-keyword">to</span>                         + msg.target.getClass().getName() +                          + msg.callback +  what= + msg.what);            }            msg.recycleUnchecked();        }    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li></ul>

分析:注释开头就描述了,该方法的作用是为了在线程中构建一个消息循环机制,当然你可以调用Looper类中的quit()成员方法结束当前loop循环。

代码第10行:获得当前线程Thread的消息队列并且赋值给本地变量queue。

代码第17-49行:构建一个死循环来遍历当前Looper中的消息队列。

代码第18-22行:每次遍历都获取消息队列中最前端的消息,也就是延时时间最短的消息。当从消息队列中获取的消息为空,则说明该消息队列已经退出。

代码第31行:这一行是重点,此处调用了当前消息的目标对象去分发消息。有1-1小节的最后一段我们知道,消息队列的目标对象就是Handler对象,因此这里就是调用Handler去调度消息或者叫分发消息。有此处也看出来,Message消息是有哪个Handler发送的,就有哪个Handler去分发消息。

跟踪代码进入

<code class="hljs avrasm has-numbering">msg<span class="hljs-preprocessor">.target</span><span class="hljs-preprocessor">.dispatchMessage</span>(msg)<span class="hljs-comment">;</span></code><ul style="" class="pre-numbering"><li>1</li></ul><ul style="" class="pre-numbering"><li>1</li></ul>

以上方法在Handler类中

<code class="hljs java has-numbering"> <span class="hljs-javadoc">/**     * Handle system messages here.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">dispatchMessage</span>(Message msg) {        <span class="hljs-keyword">if</span> (msg.callback != <span class="hljs-keyword">null</span>) {            handleCallback(msg);        } <span class="hljs-keyword">else</span> {            <span class="hljs-keyword">if</span> (mCallback != <span class="hljs-keyword">null</span>) {                <span class="hljs-keyword">if</span> (mCallback.handleMessage(msg)) {                    <span class="hljs-keyword">return</span>;                }            }            handleMessage(msg);        }    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>

该方法就是处理系统消息的地方,以上代码有三个分支,我们先看最后一个分支,就是执行handleMessage(msg);方法,跟踪代码:

<code class="hljs java has-numbering"><span class="hljs-javadoc">/**     * Subclasses must implement this to receive messages.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>

我擦,这么简单,就一个空方法?那怎么处理消息呢?注释已经告诉我们了,Handler的子类必须重写该方法用来接收消息,也就是说最后的消息

处理逻辑是延伸给Handler类的之类了。相信读者对该方法应该不陌生吧,就是我们经常在实现Handler类中重写的那个方法,消息的处理就在这个方法里面实现了。

到此,消息的调用/消息的分发就结束了,最后通过一个空方法将消息的处理逻辑留给了Handler的之类。

1-4消息的处理

消息有以上三步操作,最后进入消息的处理阶段,这里就到了我们非常熟悉的地方了,消息的处理是留给我们Handler子类去实现的,也就是我们平时编写Handler消息那样:

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> Handler mHandler;mHandler = <span class="hljs-keyword">new</span> Handler() {         <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {             <span class="hljs-comment">// process incoming messages here</span>        }};</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>

Handler消息执行过程总结:

下面用一张图来描述消息的发送,消息的入列,消息的分发,消息的处理四个过程。

这里写图片描述

有线程中的Handler把Message类封装的消息发送到当前线程的Looper对象中的消息队列MessageQueue中,然后Looper遍历循环消息队列MessageQueue,从消息队列中取出消息,通过target Handler将消息分发出去,最后当前线程中的Handler获取到该消息,并对消息进行处理。

Handler有关的构造方法

分析Handler类源码发现,Handller类有很多个构造方法。爱思考的你肯定会问,这些构造方法有什么不同呢?那么我们就从代码中来分析他们之间的不同吧:

2-1 不带参的构造方法

<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> Handler mHandler = <span class="hljs-keyword">new</span> Handler() {        <span class="hljs-annotation">@Override</span>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {            <span class="hljs-comment">//接收消息之后,处理消息</span>        }    };</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

我们一般在UI线程中这么来实例化一个Handler对象,然后通过重写Handler类中的handleMessage(Message msg)方法来处理消息。

2-2带一个参数Callback的构造方法

<code class="hljs java has-numbering"><span class="hljs-keyword">private</span> Handler handler1 = <span class="hljs-keyword">new</span> Handler(<span class="hljs-keyword">new</span> Handler.Callback() {        <span class="hljs-annotation">@Override</span>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">handleMessage</span>(Message msg) {            <span class="hljs-comment">//接收消息之后,处理消息</span>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;        }    });</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>

此处的Callback参数是Handler类的内部回调接口,我们通过实现Callback接口中的handleMessage(Message msg)方法来处理消息。

2-3带两个参数的构造方法

<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> Handler handler2 = <span class="hljs-keyword">new</span> Handler(Looper.myLooper(), <span class="hljs-keyword">new</span> Handler.Callback() {        <span class="hljs-annotation">@Override</span>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">handleMessage</span>(Message msg) {            <span class="hljs-comment">//TODO 接收消息之后,处理消息</span>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;        }    });</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>

第一个参数是当前线程的Looper对象,第二个参数是Callback接口,消息的处理和2-2小节一样,唯一不同的地方就是多了一个Looper参数。

总结

由1-3小节我们发现,Handler类中的dispatchMessage方法分发消息有几种情况:

<code class="hljs java has-numbering"> <span class="hljs-javadoc">/**     * Handle system messages here.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">dispatchMessage</span>(Message msg) {        <span class="hljs-comment">//第一种处理消息方式</span>        <span class="hljs-keyword">if</span> (msg.callback != <span class="hljs-keyword">null</span>) {            handleCallback(msg);        } <span class="hljs-keyword">else</span> {            <span class="hljs-keyword">if</span> (mCallback != <span class="hljs-keyword">null</span>) {                <span class="hljs-comment">//第二种处理消息方式</span>                <span class="hljs-keyword">if</span> (mCallback.handleMessage(msg)) {                    <span class="hljs-keyword">return</span>;                }            }            <span class="hljs-comment">//第三种处理消息方式</span>            handleMessage(msg);        }    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>

1.而当你使用2-1小节的构造方法创建Handler对象时,就是采用上面代码中的第三种处理消息的方式了,即重写Handler类中的handleMessage(msg)方法。

2.当你使用2-2和2-3小节的构造方法创建Handler对象时,就采用上面代码中的第二中处理消息的方式,即实现Handler类中的Callback接口中的

handleMessage(msg)方法。值得注意的是,如果Callback接口回调处理消息返回值是false的话,此消息还会调用第三种处理消息方式,因此没有特殊需求,我们一般在实现Callback接口回调方法时都返回true。

3.那么上面代码第一种处理消息的方式handleCallback(msg)是什么时候触发的呢?上面代码加了一个if条件判断,只有当msg消息中的成员变量

callback不为空时才调用该方式处理消息。那什么时候callback不为空呢?带着这个问题继续分析Handler发送消息的方法。

Handler发送消息的方法

handler发送消息的方法有多达11种,一听吓一跳,这么多方法怎么区分?不用怕,我把这些方法分为如下两类。

  • send系列
  • post系列

3-1 send系列

<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> Handler mHandler = <span class="hljs-keyword">new</span> Handler() {            <span class="hljs-annotation">@Override</span>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {            <span class="hljs-comment">//接收消息之后,处理消息</span>            }        };        Message msg = mHandler.obtainMessage();        <span class="hljs-comment">//发送一个消息码为0的空消息</span>        mHandler.sendEmptyMessage(<span class="hljs-number">0</span>);        <span class="hljs-comment">//发送一个消息码为0,绝对时间点为1000ms的空消息,该时间应该大于等于当前时间,下同</span>        mHandler.sendEmptyMessageAtTime(<span class="hljs-number">0</span>, <span class="hljs-number">1000</span>);        <span class="hljs-comment">//发送一个消息码为0,延时时间为1000ms的空消息</span>        mHandler.sendEmptyMessageDelayed(<span class="hljs-number">0</span>, <span class="hljs-number">1000</span>);        <span class="hljs-comment">//发送一个没有延时的msg封装的消息</span>        mHandler.sendMessage(msg);        <span class="hljs-comment">//发送一个绝对时间点为1000ms的msg封装的消息</span>        mHandler.sendMessageAtTime(msg, <span class="hljs-number">1000</span>);        <span class="hljs-comment">//发送一个延时时间为1000ms的msg封装的消息</span>        mHandler.sendMessageDelayed(msg, <span class="hljs-number">1000</span>);        <span class="hljs-comment">//立即发送一个msg封装的消息到消息队列的最前端</span>        mHandler.sendMessageAtFrontOfQueue(msg);</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul>

分析源码你会发现,除了最后一个方法,以上所有的方法最后都会调用如下方法:

<code class="hljs applescript has-numbering">/**     * Enqueue a message <span class="hljs-keyword">into</span> <span class="hljs-keyword">the</span> message queue <span class="hljs-keyword">after</span> all pending messages     * <span class="hljs-keyword">before</span> <span class="hljs-keyword">the</span> absolute <span class="hljs-property">time</span> (<span class="hljs-keyword">in</span> milliseconds) <var>uptimeMillis</var>.     * <b>The <span class="hljs-property">time</span>-base <span class="hljs-keyword">is</span> {@link android.os.SystemClock<span class="hljs-comment">#uptimeMillis}.</b></span>     * Time spent <span class="hljs-keyword">in</span> deep sleep will add an additional <span class="hljs-command">delay</span> <span class="hljs-keyword">to</span> execution.     * You will receive <span class="hljs-keyword">it</span> <span class="hljs-keyword">in</span> {@link <span class="hljs-comment">#handleMessage}, in the thread attached</span>     * <span class="hljs-keyword">to</span> this handler.     *      * @param uptimeMillis The absolute <span class="hljs-property">time</span> <span class="hljs-keyword">at</span> which <span class="hljs-keyword">the</span> message should be     *         delivered, using <span class="hljs-keyword">the</span>     *         {@link android.os.SystemClock<span class="hljs-comment">#uptimeMillis} time-base.</span>     *              * @<span class="hljs-constant">return</span> Returns <span class="hljs-constant">true</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">the</span> message was successfully placed <span class="hljs-keyword">in</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">the</span>      *         message queue.  Returns <span class="hljs-constant">false</span> <span class="hljs-function_start"><span class="hljs-keyword">on</span></span> failure, usually because <span class="hljs-keyword">the</span>     *         looper processing <span class="hljs-keyword">the</span> message queue <span class="hljs-keyword">is</span> exiting.  Note <span class="hljs-keyword">that</span> a     *         <span class="hljs-constant">result</span> <span class="hljs-keyword">of</span> <span class="hljs-constant">true</span> <span class="hljs-keyword">does</span> <span class="hljs-keyword">not</span> mean <span class="hljs-keyword">the</span> message will be processed <span class="hljs-comment">-- if</span>     *         <span class="hljs-keyword">the</span> looper <span class="hljs-keyword">is</span> quit <span class="hljs-keyword">before</span> <span class="hljs-keyword">the</span> delivery <span class="hljs-property">time</span> <span class="hljs-keyword">of</span> <span class="hljs-keyword">the</span> message     *         occurs <span class="hljs-keyword">then</span> <span class="hljs-keyword">the</span> message will be dropped.     */    public <span class="hljs-type">boolean</span> sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue;        <span class="hljs-keyword">if</span> (queue == null) {            RuntimeException e = new RuntimeException(                    this +  sendMessageAtTime() called <span class="hljs-keyword">with</span> no mQueue);            Log.w(Looper, e.getMessage(), e);<span class="hljs-command">            return</span> <span class="hljs-constant">false</span>;        }<span class="hljs-command">        return</span> enqueueMessage(queue, msg, uptimeMillis);    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul>

分析:以上注释都解释的很清楚,第一个参数就是Message类封装的消息,第二个参数就是消息的更新时间,该时间是绝对时间。

3-2 post系列

<code class="hljs java has-numbering">  mHandler = <span class="hljs-keyword">new</span> Handler();        mHandler.post(<span class="hljs-keyword">new</span> Runnable() {            <span class="hljs-annotation">@Override</span>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {                <span class="hljs-comment">//TODO 接收消息之后,处理消息</span>            }        });        mHandler.postAtTime(<span class="hljs-keyword">new</span> Runnable() {            <span class="hljs-annotation">@Override</span>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {                <span class="hljs-comment">//TODO 接收消息之后,处理消息</span>            }        }, <span class="hljs-keyword">new</span> Object(), <span class="hljs-number">1000</span>);        mHandler.postDelayed(<span class="hljs-keyword">new</span> Runnable() {            <span class="hljs-annotation">@Override</span>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {                <span class="hljs-comment">//TODO 接收消息之后,处理消息</span>            }        }, <span class="hljs-number">1000</span>);        mHandler.postAtFrontOfQueue(<span class="hljs-keyword">new</span> Runnable() {            <span class="hljs-annotation">@Override</span>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {                <span class="hljs-comment">//TODO 接收消息之后,处理消息</span>            }        });</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul>

分析:和send系列大有不同,此处我们的Handler无需在去重写handlerMessage方法来处理消息了,此处将接口类Runnable作为消息发送,然后实现Runnable接口类中的run方法来处理消息。对于这一点我相信很多初学者会有疑问,这里又没有Message封装消息,怎么将Runnable接口类当作消息发送呢? 一句话,还是跟踪源码看看:

<code class="hljs applescript has-numbering">  /**     * Causes <span class="hljs-keyword">the</span> Runnable r <span class="hljs-keyword">to</span> be added <span class="hljs-keyword">to</span> <span class="hljs-keyword">the</span> message queue, <span class="hljs-keyword">to</span> be <span class="hljs-command">run</span>     * <span class="hljs-keyword">at</span> a specific <span class="hljs-property">time</span> <span class="hljs-keyword">given</span> <span class="hljs-keyword">by</span> <var>uptimeMillis</var>.     * <b>The <span class="hljs-property">time</span>-base <span class="hljs-keyword">is</span> {@link android.os.SystemClock<span class="hljs-comment">#uptimeMillis}.</b></span>     * Time spent <span class="hljs-keyword">in</span> deep sleep will add an additional <span class="hljs-command">delay</span> <span class="hljs-keyword">to</span> execution.     * The runnable will be <span class="hljs-command">run</span> <span class="hljs-function_start"><span class="hljs-keyword">on</span></span> <span class="hljs-keyword">the</span> thread <span class="hljs-keyword">to</span> which this handler <span class="hljs-keyword">is</span> attached.     *     * @param r The Runnable <span class="hljs-keyword">that</span> will be executed.     * @param uptimeMillis The absolute <span class="hljs-property">time</span> <span class="hljs-keyword">at</span> which <span class="hljs-keyword">the</span> callback should <span class="hljs-command">run</span>,     *         using <span class="hljs-keyword">the</span> {@link android.os.SystemClock<span class="hljs-comment">#uptimeMillis} time-base.</span>     *      * @<span class="hljs-constant">return</span> Returns <span class="hljs-constant">true</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">the</span> Runnable was successfully placed <span class="hljs-keyword">in</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">the</span>      *         message queue.  Returns <span class="hljs-constant">false</span> <span class="hljs-function_start"><span class="hljs-keyword">on</span></span> failure, usually because <span class="hljs-keyword">the</span>     *         looper processing <span class="hljs-keyword">the</span> message queue <span class="hljs-keyword">is</span> exiting.  Note <span class="hljs-keyword">that</span> a     *         <span class="hljs-constant">result</span> <span class="hljs-keyword">of</span> <span class="hljs-constant">true</span> <span class="hljs-keyword">does</span> <span class="hljs-keyword">not</span> mean <span class="hljs-keyword">the</span> Runnable will be processed <span class="hljs-comment">-- if</span>     *         <span class="hljs-keyword">the</span> looper <span class="hljs-keyword">is</span> quit <span class="hljs-keyword">before</span> <span class="hljs-keyword">the</span> delivery <span class="hljs-property">time</span> <span class="hljs-keyword">of</span> <span class="hljs-keyword">the</span> message     *         occurs <span class="hljs-keyword">then</span> <span class="hljs-keyword">the</span> message will be dropped.     *              * @see android.os.SystemClock<span class="hljs-comment">#uptimeMillis</span>     */    public final <span class="hljs-type">boolean</span> postAtTime(Runnable r, Object token, long uptimeMillis)    {<span class="hljs-command">        return</span> sendMessageAtTime(getPostMessage(r, token), uptimeMillis);    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul>

分析:不管post系列调用哪个方法,最终都会调用上面这个方法来发送消息,你会惊奇的发现,原来post系列也是调用send系列的方法发送方法的。只不过此处调用了getPostMessage方法将Runnable对象转换成Message对象而已。跟踪代码进入getPostMessage方法:

<code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Message <span class="hljs-title">getPostMessage</span>(Runnable r, Object token) {        Message m = Message.obtain();        m.obj = token;        m.callback = r;        <span class="hljs-keyword">return</span> m;    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

看到没?调用此方法,将Runnable对象r赋值给了Message类中的clalback成员变量。现在我们回顾一下2-3小节Handler类中的

dispatchMessage方法处理消息的三种方式,此处将Runnable接口对象赋值之后Message类中的msg.callback就不会为空,因此调用第一种处理消息的方式。

第3节总结:

不管3-1或者3-2小节调用那种方式发送消息,最终都会调用Handler类中的enqueueMessage成员方法,将消息按照消息更新时间顺序插入到消息队列中,这个过程的分析可以参考1-1小节最后一段内容。

在Message消息封装者类中有这么一段注释:

<code class="hljs applescript has-numbering"> /*While <span class="hljs-keyword">the</span> constructor <span class="hljs-keyword">of</span> Message <span class="hljs-keyword">is</span> public, <span class="hljs-keyword">the</span> best way <span class="hljs-keyword">to</span> <span class="hljs-keyword">get</span> * one <span class="hljs-keyword">of</span> these <span class="hljs-keyword">is</span> <span class="hljs-keyword">to</span> call {@link <span class="hljs-comment">#obtain Message.obtain()} or one of the</span> * {@link Handler<span class="hljs-comment">#obtainMessage Handler.obtainMessage()} methods, which will pull</span> * them <span class="hljs-keyword">from</span> a pool <span class="hljs-keyword">of</span> recycled objects*/</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>

这段话告诉我们,当你需要构造一个Message消息对象的时候,最好的方法是调用Message.obtain()或者Handler.obtainMessage()方法来获得,而不是这样获得:

<code class="hljs mathematica has-numbering"><span class="hljs-keyword">Message</span> msg1 = new <span class="hljs-keyword">Message</span>();</code><ul style="" class="pre-numbering"><li>1</li></ul><ul style="" class="pre-numbering"><li>1</li></ul>

为什么这样获得Message对象不好?我们跟踪源码瞧瞧不就知道了。

<code class="hljs java has-numbering"><span class="hljs-javadoc">/**     * Return a new Message instance from the global pool. Allows us to     * avoid allocating new objects in many cases.     */</span>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Message <span class="hljs-title">obtain</span>() {        <span class="hljs-keyword">synchronized</span> (sPoolSync) {            <span class="hljs-keyword">if</span> (sPool != <span class="hljs-keyword">null</span>) {                Message m = sPool;                sPool = m.next;                m.next = <span class="hljs-keyword">null</span>;                m.flags = <span class="hljs-number">0</span>; <span class="hljs-comment">// clear in-use flag</span>                sPoolSize--;                <span class="hljs-keyword">return</span> m;            }        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Message();    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul>

这是一个Message类中的静态方法,方法开头解释的很清楚:从全局的消息池中获得一个新的消息示例,避免用户多次创建Message消息实例浪费

内存。只有当全局的消息池中没有可用的消息实例,才去调用new 一个新的Message消息实例。总之,归纳成一句话,调用Message.obtain()或

者Handler.obtainMessage()方法避免多次创建Message实例,这样性能更加优化。

Handler移除消息的方法以及Handler内存泄漏问题

方法包括如下:

<code class="hljs vala has-numbering"><span class="hljs-comment">//从消息队列中移出post系列方法中Runnable对象的消息</span> mHandler.removeCallbacks(Runnable r); <span class="hljs-comment">//从消息队列中移除消息码为“what”的消息</span> mHandler.removeMessages(<span class="hljs-keyword">int</span> what); <span class="hljs-comment">//从消息队列中移除所有post系列方法和send系列方法发送的消息</span> mHandler.removeCallbacksAndMessages(<span class="hljs-built_in">Object</span> token);</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

可能很初学者会有疑问,开发中很少用到以上这些方法。那如果你很少使用以上方法,说明你还是一个Android小白。如果不调用以上相应的方法

去移除消息,就会存在Handler对象内存泄漏的隐患。此话怎讲?我在一次项目开发中遇到这样的问题:

我开启一个新的工作线程WorkThread去执行一段耗时网络请求,当请求结束之后我调用UI线程中的Handler发送一个Message消息给UI线程,但

是此时,我早已经退出当前持有Handler对象的Activity,这会持有Handler的Activity是接收不到WorkThread发送过来的消息的,因为

Activity已经退出,因此会出现一个bug:Handler对象内存泄露。那怎么解决这个Handler内存泄露隐患呢?

4-1

很简单,在持有Handler对象的Activity的onDestory方法调用如下代码去移除消息队列中的所有消息

<code class="hljs cs has-numbering">mHandler.removeCallbacksAndMessages(<span class="hljs-keyword">null</span>);</code><ul style="" class="pre-numbering"><li>1</li></ul><ul style="" class="pre-numbering"><li>1</li></ul>

此方法只要传递一个null空参数就表示移出当前线程中消息队列中所有的消息。有些人可能会说,我并不想清除消息队列,可能还有其他Activity等待接收消息,好办。你可以调用如下相应的方法移除消息队列中相应的消息:

<code class="hljs cs has-numbering"><span class="hljs-comment">//从消息队列中移出post系列方法中Runnable对象的消息</span> mHandler.removeCallbacks(Runnable r); <span class="hljs-comment">//从消息队列中移除消息码为“what”的消息</span> mHandler.removeMessages(<span class="hljs-keyword">int</span> what);</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

4-2

那么我们是否还有其他办法解决Handler内存泄漏问题呢?答案是肯定的:将Handler申明为静态的,因为静态类不持有外部类的引用。

<code class="hljs axapta has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SubActivity</span> <span class="hljs-inheritance"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">Activity</span> {</span>  <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StaticHandler</span> <span class="hljs-inheritance"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">Handler</span> {</span>        WeakReference mActivityReference;        StaticHandler(Activity activity) {            mActivityReference= <span class="hljs-keyword">new</span> WeakReference(activity);        }        @Override        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> handleMessage(Message msg) {            <span class="hljs-keyword">final</span> SubActivity activity = (SubActivity) mActivityReference.get();            <span class="hljs-keyword">if</span> (activity != <span class="hljs-keyword">null</span>) {                activity.textView.setText(测试静态Handler解决内存泄漏问题);            }        }    }}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li></ul>

UI线程在使用Handler之前不需要Looper.prepare的原因

文章一开头由“一个Bug引发的思考”我们知道,在线程中使用Handler之前需要调用Looper.prepare方法。爱思考的你会很纳闷,那为什么我

们的UI线程在创建Handler对象的时候没有调用Looper.prepare方法呢?此时我们不得不从UI线程是怎么来的说起了。不过这里不展开讨论,直接给出答案,我们创建一个Application的时候,都是由系统的一个ActivityThread类来启动的。跟踪代码进入ActivityThread类:

<code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> final <span class="hljs-keyword">class</span> ActivityThread {...................    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) {            ..............            Looper.prepareMainLooper();            ..............    }}</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>

该类中有一个main方法,是不是感觉很熟悉啊?对了,这里就是我们整个Application应用的入口了。在main方法中调用了Looper.prepareMainLooper(),猜测该方法里面就调用了Looper.prepare方法来为线程循环loop准备。跟踪源码:

<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">prepareMainLooper</span>() {        prepare(<span class="hljs-keyword">false</span>);        <span class="hljs-keyword">synchronized</span> (Looper.class) {            <span class="hljs-keyword">if</span> (sMainLooper != <span class="hljs-keyword">null</span>) {                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(The main Looper has already been prepared.);            }            sMainLooper = myLooper();        }    }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>

果然,里面调用了prepare方法。到此,我们就知道其实在UI线程中系统在ActivityThread类中就帮我们调用了Looper.prepare方法为UI线程loop循环做准备。

HandlerThread帮你创建循环消息处理机制

你可能会遇到这样一种情况,在UI线程中创建一个工作线程WorkThread执行耗时任务。在正常情况下,这个WorkThread执行完任务之后就销毁

了。但是你过了一段时间又有一个耗时任务需要执行,这会你会怎么办?你只能重新去创建一个WorkThread执行耗时任务。但是线程Thread的每

次创建和销毁都是很耗系统资源的,因此在这种情况下,我们的HandlerThread就此诞生了。HandelrThread是继承自Thread实现了,

也就是说HandlerThread是一个线程。只是该线程里面帮你实现了Looper循环机制,从而使得该线程变成带有循环机制的线程。言外之

意:当该线程执行完一个耗时任务之后不会马上被销毁,除了用户主动调用HandlerThread类中的quit成员方法来退出当前loop循环。关于

HandlerTread是怎么使用的?可以参考我的另一篇博客:
http://blog.csdn.net/feiduclear_up/article/details/46840523

总结:

通过以上文章分析,基本了解了Android系统的消息处理机制是怎么一回事,也知道了怎么使用Handler,以及Handler使用的一些陷阱等。来总结一下 Handler,Message,Looper,MessageQueue,Thread之间的关联吧。

如果一个线程Thread想要使用Handler类来发送消息,就必须先调用Looper类中的prepare成员方法来做一些准备工作,然后调用Looper类

中的loop方法启动该线程的循环机制。Handler类把封装成Message类的消息发送到Looper类中的消息队列中,然后Looper类中的loop循环方法

中分发消息,最后将相应的消息对象分发到相应的target handler类中去处理消息。

由此我们知道:一个线程Thread中只能拥有一个Looper对象,且一个Looper对象中只能拥有一个MessageQueue消息队列,但是一个Thread

线程中可以有多个Handler对象,而多个Handler对象共享同一个MessageQueue消息队列。最后提供一个Handler消息处理机制的流程图

这里写图片描述

0 0
原创粉丝点击