hanlder机制(小白也能看的懂)

一.背景

  总结了很多大神对handler机制(感谢各位大神),很早以前就写了,最近回想居然想不起来,于是在硬盘的某个阴暗角落把他找出来了,方便以后查阅,写成博客,方便自己,也方便大家大笑

二.概述

        Android使用消息机制实现线程间的通信,线程通过Looper建立自己的消息循环,MessageQueue是FIFO的消息队列,Looper负责从MessageQueue中取出消息,并且分发到消息指定目标Handler对象。Handler对象绑定到线程的局部变量Looper,封装了发送消息和处理消息的接口

三.例子

      这个例子实现点击按钮之后从主线程发送消息"hello"到另外一个名为” CustomThread”的线程。

 class CustomThread extends Thread {
        @Override
        public void run() {

//建立消息循环的步骤

//1.初始化Looper(下面有解释具体过程)

     Looper.prepare();//1、初始化Looper

//2.绑定handler到CustonThread实例的Looper(下面有解释具体过程)     

            mHandler = new Handler(){
//3.定义处理消息的方法   

                public void handleMessage (Message msg) {//3、定义处理消息的方法
                    switch(msg.what) {
                    case MSG_HELLO:
                        Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);
                    }
                }
            };
//4.启动消息循环(下面有解释具体过程)

           Looper.loop();
        }
}

我们看到,为一个线程建立消息循环有四个步骤:

1、 初始化Looper

//Looper.java

 /** 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() {
        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));
    }

2、 绑定handler到CustomThread实例的Looper对象

一个线程在调用Looper的静态方法prepare()时,这个线程会新建一个Looper对象,并放入到线程的局部变量中,而且这个变量是不和其他线程共享的(关于ThreadLocal的介绍)。

下面我们看看Looper()这个构造函数:

final MessageQueue mQueue;
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
}

可以看到在Looper的构造函数中,创建了一个消息队列对象mQueue,此时,调用Looper. prepare()的线程就建立起一个消息循环的对象(此时还没开始进行消息循环,需要调用loop才会开始循环)。

//handler.java

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
 public Handler() {
        this(null, false);
    }
然后就会调用这个
  public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();<pre name="code" class="java">
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; }
 
  public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Handler通过mLooper = Looper.myLooper();绑定到线程的局部变量mLooper上去,同时Handler通过mQueue =mLooper.mQueue;获得线程的消息队列。此时,Handler就绑定到创建此Handler对象的线程的消息队列上了

3、 定义处理消息的方法

子类需要覆盖这个方法,实现接受到消息后的处理方法。

4、 启动消息循环

所有准备工作都准备好了,是时候启动消息循环了!Looper的静态方法loop()实现了消息循环。

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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
            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();
        }
    }

while(true)体现了消息循环中的“循环“,Looper会在循环体中调用queue.next()获取消息队列中需要处理的下一条消息。当msg != null且msg.target != null时,调用msg.target.dispatchMessage(msg);分发消息,当分发完成后,调用msg.recycle();回收消息

msg.target是一个handler对象,表示需要处理这个消息的handler对象。

//handler.java

  public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

  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);
    }

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
       if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

然后就把当前调用的handler传给msg.tartget

下面是分发消息

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

可见,当msg.callback== null 并且mCallback == null时(msg.callback 就是Handler的post方法传递的Runable,mCallback 是 Handler handler = new Handler(callback)),这个例子是由handleMessage(msg);处理消息,上面我们说到子类覆盖这个方法可以实现消息的具体处理过程。

总结:从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。

method1:

  private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

method2:

 Handler handler = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        return false;
                    }
                });

method3:

    new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {             
                    }
                }, 200);



四.总结

职责

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。

同时通过sendmessage来指定msg.target(把当前handler设置为msg.target)

MessageQueue:消息队列(采用单链表的数据结构来存储信息),用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper

Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

关系

HandlerLooperMessageQueue就是简单的三角关系。LooperMessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue。而Handler与它们的关系,只是简单的聚集关系,Handler里会引用当前线程里的特定LooperMessageQueue

这样说来,多个Handler都可以共享同一LooperMessageQueue了。当然,这些Handler也就运行在同一个线程里(looper是绑定当前线程)(但是在handler.sendmessage中已经把msg.target,设定为当前handler对象,然后放在消息队列中,然后looper.loop()中轮询消息队列,获得消息,可以调用msg.target来handlemessage)

 

 

 如何更新UI

剩下的部分,我们将讨论一下Handler所处的线程及更新UI的方式。

在主线程(UI线程)里,如果创建Handler时不传入Looper对象,那么将直接使用主线程(UI线程)的Looper对象(系统已经帮我们创建了);在其它线程里,如果创建Handler时不传入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();
        }
    }

      在创建Handler之前,为该线程准备好一个LooperLooper.prepare),然后让这个Looper跑起来(Looper.loop),抽取Message,这样,Handler才能正常工作。

因此,Handler处理消息总是在创建Handler的线程里运行。而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的

注意:

如果手动创建Looper,那么所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态,而且如果退出Looper之后,这个线程会立即终止,因此建议不需要的时候终止Looper.

  (1)quit  直接退出

  (2)quitsafely   只是设定了一个退出标记,然后把消息队列中的已有消息处理完毕后才能安全退出

 还有Looper.loop是一个死循环,唯一跳出循环的方式就是MessageQueue的next方法返回null(只有当Looper的quit方法被调用时,才会让next方法返回null,也就是说Looper必须退出),否则loop方法就会无限循环下去,而且当没有消息的时候,next方法就会一直阻塞在那里,也就导致loop方法一直阻塞在那里

 

如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程:

·      Activity.runOnUiThread(Runnable)

·      View.post(Runnable) (把消息放到UI线程的消息循环中)

·      View.postDelayed(Runnable, long)

·      Handler

其中,重点说一下的是View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象postHandler里。在Handler里,它将传递过来的action对象包装成一个MessageMessagecallbackaction),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnablerun方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI

 几点小结

·      Handler的处理过程运行在创建Handler的线程里

·      一个Looper对应一个MessageQueue

·      一个线程对应一个Looper

·      一个Looper可以对应多个Handler

·      不确定当前线程时,更新UI时尽量调用post方法





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值