Handler详解系列(一)——Handler异步消息机制详解(附图)

来源:互联网 发布:数据标注是什么 编辑:程序博客网 时间:2024/06/05 05:58

MainActivity如下:

package cc.cn;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.util.Log;import android.app.Activity;/** * Demo描述:  * Android异步消息机制分析(附图) *  * ======================================================= *  * 问题的引入: * 在子线程中直接调用Handler handler=new Handler()此时报错: * Can't create handler inside thread that has not called Looper.prepare(). * 既然是在调用Handler的构造方法时报的错那就去看该构造方法的源码. * public Handler() { *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 = null; *} * * 从以上代码mLooper = Looper.myLooper()可以看到当mLooper==null时就会报错 * Can't create handler inside thread that has not called Looper.prepare(). *  *  * 继续看Looper.myLooper()的源码. * *Return the Looper object associated with the current thread. * *Returns null if the calling thread is not associated with a Looper. *  public static Looper myLooper() { *       return sThreadLocal.get(); *   } * 请看源码中的这两行注释: * 返回与当前线程相关联的Looper. * 如果当前线程没有一个与之相关联的Looper那么就返回null. *  * 回看刚才的报错Can't create handler inside thread that has not called Looper.prepare(). * 提示我们调用Looper.prepare(). * 于是在子线程中这么写: * Looper.prepare(); * Handler handler=new Handler(); * 该报错消失. *  * 既然调用Looper.prepare()可以消除该错误. * 那就看看Looper.prepare()的源码. *  *  /** 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()}. *  public static void prepare() { *      if (sThreadLocal.get() != null) { *         throw new RuntimeException("Only one Looper may be created per thread"); *       } *       sThreadLocal.set(new Looper()); *  } *  从这段源码可以看出以下几点: *  1 从注释文档可以看出 *    1.1 该prepare()方法的作用: *        利用一个looper来初始化当前线程,或者说初始化一个带有Looper的线程. *        这样就使得一个线程和属于它的Looper关联起来. *    1.2 在调用该prepare()方法后需要调用loop()方法开始消息的轮询,也可调用quit()方法停止消息的轮询. *  2 从代码中可以看出 *    2.1 注意该方法的异常提示: *        Only one Looper may be created per thread *        一个线程只能创建一个Looper!!!!!! *    2.2 在1.1中提到: *        "prepare()方法的作用,利用一个looper来初始化当前线程,或者说初始化一个带有Looper的线程.这样就使得一个线程和属于它的Looper关联起来." *        它的代码体现就是sThreadLocal.set(new Looper())这句代码. *        其中Looper构造方法如下: *        private Looper() { *           mQueue = new MessageQueue(); *           mRun = true; *           mThread = Thread.currentThread(); *        } *        从此可以看到sThreadLocal.set(new Looper())这句代码的两个操作 *        (1) 构造一个Looper *        (2) 将此Looper保存到sThreadLocal中,所以在上述的myLooper()方法中调用sThreadLocal.get()就不再为空. *            换句话说将此Looper保存到sThreadLocal中也确保了一个线程只有一个Looper. *             *        看到这里的时候或许有点疑问——凭啥说"将此Looper保存到sThreadLocal中也确保了一个线程只有一个Looper."??? *        那就重新仔细看sThreadLocal.set(new Looper())这句代码的两个操作,同上. *        (1) 构造一个Looper(即new Looper()) *            一个Looper包含了三个变量: *            mQueue---->MessageQueue(消息队列) *            mRun------>true *            mThread--->Thread.currentThread()(当前线程) *            这三个变量中最重要的是mQueue(消息队列)和mThread(当前线程),这样就使得一个Looper和一个消息队列以及当前线程对应起来. *        (2) 将此Looper保存到sThreadLocal中(即sThreadLocal.set(new Looper())) *            请注意sThreadLocal这个变量的声明和定义: *            static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); *            它是Looper类的全局的static final变量,它在类的加载的时候就已经存在了而且它是final的. *            这就是说sThreadLocal本身是唯一的不可变的!!!!!!!!!!再看代码: *            public static void prepare() { *               if (sThreadLocal.get() != null) { *               throw new RuntimeException("Only one Looper may be created per thread"); *             } *               sThreadLocal.set(new Looper()); *            } *            在调用Looper.prepare()方法时先会使用if (sThreadLocal.get() != null) *            判断sThreadLocal中是否已经保存了Looper. *            如果已经保存了Looper则会报错:Only one Looper may be created per thread.一个线程只能创建一个Looper *            如果没有保存Looper,才会去调用sThreadLocal.set(new Looper()); *            这样就确保了当前线程和Looper的唯一对应. *             *   至此可以看到: *   1 一个线程对应一个Looper. *     那么说一个线程对应一个Handler,这么说对不?这是不对的.因为可以在一个线程中创建几个Handler对象,但一个线程只有一个Looper. *     所以线程和Handler不是一一对应的,一个线程中可以创建多个Handler(当然这样做,实际意义不大);且在同一线程创建多个Handler时 *     每个Handler使用的是同一个Looper. *     类似的说法:线程绑定了Handler,这也是不正确的. *   2 该Looper又对应一个消息队列和当前线程 *   简单地说就是:一个线程,对应一个Looper,对应一个消息队列.三者一一对应.          *             *           *   *  至此一个子线程中使用Handler的方式应该是这样的: *  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(); *      } *  } *  该代码也是Google推荐的写法. *   *   *  在平常使的MainActivity中的UI线程中使用Handler时并没有调用Looper.prepare(); *  这是为什么呢? *  因为UI线程是主线程,系统已经自动帮我们调用了Looper.prepare()方法. *  在此不再赘述. *   *   *   *  以上讨论了线程(Thread)和Looper的使用,下面讨论消息的发送和处理过程. *  平常最常用的方式: *  handler.sendMessage(message)---->发送消息 *  handleMessage(Message msg)------>处理消息 *   *  那么handleMessage(Message msg)中是怎么获取到刚发出的这条消息呢??? *  Handler发送消息的具体方法有好几个但除了sendMessageAtFrontOfQueue(Message msg) *  以外的几个方法最终会调用sendMessageAtTime(Message msg, long uptimeMillis)方法: *   *  public boolean sendMessageAtTime(Message msg, long uptimeMillis){ *      boolean sent = false; *      MessageQueue queue = mQueue; *      if (queue != null) { *          msg.target = this; *          sent = queue.enqueueMessage(msg, uptimeMillis); *      } *      else { *          RuntimeException e = new RuntimeException( *          this + " sendMessageAtTime() called with no mQueue"); *          Log.w("Looper", e.getMessage(), e); *      } *      return sent; *  } *   *  在该方法中有两句代码很重要: *  1 msg.target = this; *    给msg设置了target. *    这里的this当然就是当前Handler对象本身! *  2 sent = queue.enqueueMessage(msg, uptimeMillis); *    将消息放入消息队列中. *    这里的queue(mQueue)就是上述Looper构造方法中的mQueue. *    在enqueueMessage(msg, uptimeMillis)方法中有一个队列. *    距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端. *    若调用sendMessageAtFrontOfQueue()方法发送消息它会调用该enqueueMessage(msg, uptimeMillis) *    来让消息入队只不过时间为延迟时间为0,即它会插入到队列头部. *     *     *  这就是消息的入队操作,那么消息怎么出队呢? *  这就要看Looper中的loop()方法 *  public static final void loop() { *    Looper me = myLooper(); *    MessageQueue queue = me.mQueue; *    while (true) { *       Message msg = queue.next(); // might block *       if (msg != null) { *          if (msg.target == null) { *              return; *          } *          if (me.mLogging!= null) me.mLogging.println(  *          ">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what); *          msg.target.dispatchMessage(msg); *          if (me.mLogging!= null) me.mLogging.println( *          "<<<<< Finished to    " + msg.target + " "+ msg.callback); *          msg.recycle(); *      } *     } *   } *   在该方法中是一个死循环while(true),即Looper一直在轮询消息队列(MessageQueue) *   在该方法中有两句代码很重要: *   1 Message msg = queue.next(); *     queue.next()消息队列的出列. *   2 msg.target.dispatchMessage(msg); *     用调用msg里的target的dispatchMessage()方法. *     target是什么呢? *     参见上述sendMessageAtTime(Message msg, long uptimeMillis)可知: *     target就是Handler!!!!在此回调了Handler的dispatchMessage方法,所以该消息就发送给了对应的Handler. *     接下来看Handler的dispatchMessage(Message msg)方法: *      *  public void dispatchMessage(Message msg) { *       //1 message的callback *    if (msg.callback != null) { *       handleCallback(msg); *    } else { *       //2 handler的callback *       if (mCallback != null) { *           if (mCallback.handleMessage(msg)) { *               return; *          } *      } *      //3 Handler的handleMessage() *      handleMessage(msg); *    } *  } *   * 其中涉及到的CallBack为: * public interface Callback { *    public boolean handleMessage(Message msg); * } * Handler的其中一个构造方法为: * Handler handler=new Handler(callback); * 所以在dispatchMessage(Message msg)涉及到了CallBack * 在多数情况下message和Handler的callBack均为空 * 所以会调用dispatchMessage(Message msg)方法: * 这就回到了我们最熟悉的地方. *  *  *  * ======================================================= *  * Android异步消息机制中主要涉及到: * Thread Handler Looper MessageQueue * 它们的相互关系如下: * 1 Looper.prepare(); *   1.1 为当前线程生成对应的Looper. *       一个Looper包含了三个变量: *       mQueue---->MessageQueue(消息队列) *       mRun------>true *       mThread--->Thread.currentThread()(当前线程) *   1.2 将该Looper保存到ThreadLocal中. *       之所以采用ThreadLocal来存放线程对应的Looper主要目的是确保 *       每个线程只有一个唯一的Looper. *        *   Looper和其所属线程的相互关联的代码体现: *   1.3 Looper.myLooper() *       得到与当前线程相关联的Looper *   1.4 Looper.myLooper().getThread() *       得到与Looper相关联的线程Thread *   *  2 Handler handler=new Handler() *    在第一步中利用Looper.prepare()实现了Looper与线程的关联. *    在此接着看Handler与Looper的关系. *    注意看上述Handler的构造方法中的代码: *    mLooper = Looper.myLooper(); *    这样就得到了与线程相关联的Looper. *    mQueue = mLooper.mQueue; *    得到与线程相关联的Looper的消息队列(MessageQueue)mQueue *    在Handler的构造方法中就看出了Handler与Looper的关联. *     *  小结: *  (1) Looper.prepare(); *      实现了Looper和其所属线程的相互关联 *  (2) Handler handler=new Handler() *      实现了Handler与Looper的关联. *  (3) 一个线程可有多个Handler但只有一个Looper *   *    * ======================================================= * 了解Thread Handler Looper MessageQueue几者间的关系之后,在此总结 * Android异步消息机制的流程: *   * 1 Looper.prepare(); *   实现Looper和其所属线程的相互关联 *   一个Looper包含了三个变量: *   mQueue---->MessageQueue(消息队列) *   mRun------>true *   mThread--->Thread.currentThread()(当前线程) *   在执行该方法以后每个Looper中就存在一个消息队列(MessageQueue)了. *   Handler发送的消息都会保存到该消息队列(MessageQueue)中. * 2 Handler handler=new Handler();   *   实现了Handler与Looper的关联. * 3 Looper.loop(); *   开始轮询消息队列(MessageQueue),并且会一直轮询. *   按照队列处理里面的每个消息.当然刚开始的时候该队列为空. * 4 mHandler.sendMessage(message)发送消息至消息队列. *   具体过程可以参见以上的详细描述. * 5 在第三步时Looper.loop()不是一直在轮询消息队列么? *   当消息(Message)出队时,找到该消息的target(其实就是一个Handler)回调 *   其dispatchMessage(Message msg)方法;在该方法中会调用到我们非常熟悉 *   的handleMessage(Message msg). *   * 以上就是Android异步消息机制的详细流程. * 简单地可以这么说: * Handler------>发送消息至消息队列(MessageQueue)和处理消息 * Looper------->用消息队列(MessageQueue)保存消息.使用loop()方法一直轮询消息队列. *               并在消息出队的时候将其发送给合适的Handler. *  * 以上流程分析可参见流程图. *  * ======================================================= */public class MainActivity extends Activity {private Thread mThread;private Handler mHandler;private final String TAG = "Handler";private final int FLAG=9527;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);testHandler();}//Can't create handler inside thread that has not called Looper.prepare()private void testHandler() {mThread = new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what == FLAG) {Log.i(TAG, "收到消息  msg.arg1=" + msg.arg1);}}};Message message = new Message();message.what = FLAG;message.arg1 = 456;mHandler.sendMessage(message);Looper.loop();}});mThread.start();}}/** * 1 演示错误,引入Looper.prepare();要读该方法的注释,会引入Looper.looper()方法 *   且注意此时的代码里的报错:Only one Looper may be created per thread *   一个线程只能创建一个Looper *   可以看到sThreadLocal中保存了一个Looper. * 2 继续从Looper.prepare()中查看Looper的构造方法 *   Looper构造方法如下: *   private Looper() { *     mQueue = new MessageQueue(); *     mRun = true; *     mThread = Thread.currentThread(); *   } *    */




附图如下:


main.xml如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"   >    <TextView        android:layout_centerInParent="true"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Handler原理学习" /></RelativeLayout>


1 0