Android开发中Handler、Looper、MessageQueue的原理

今天对Android中的Handler Message Looper这三个类从源码角度深入理解其用法。handler类是Android更新ui状态经常用到的异步方式,也是android进程之间通信的一种方式,是一种异步消息处理线程,实现异步线程需要解决的具体问题如下:
1,每个异步线程内部包含一个消息队列(MessageQueue),队列的消息一般采用排队机制,就是先进先出(FIFO)。
2,线程的执行体重使用while(true)进行无限循环,循环体重从消息队列中取出消息,并根据Message的来源进行相应的回调函数处理(CallBack接口)。
3,其他外部线程可以向本线程的消息队列发送消息,消息队列内部读/写操作必须进行加锁,就是不能同时读/写操作。
为了更好理解Handler的工作原理先简单介绍下Handler、Loop、MessageQueue这三个的关系
1、MessageQueue:消息队列,采用FIFO的方式管理Message;
2、Loop:管理MessageQueue,不断从队列里面取出消息,根据Message的信息找到发送该消息的Handler的回调函数进行相应处理
3、Handler:发送Message到MessageQueue,并处理Message信息
这样一来,关于Handler来处理异步消息的过程我们可以这样总结:首先创建一个MessageQueue队列,然后Handler将Message发送到MessageQueue中,然后Loop来从MessageQueue中取出Message,根据消息的target信息去回调相应Handler的函数。接下来我们从源码去看这个过程是怎么一步一步实现的。
首先来看个简单的例子便于我们讲解:

`public class MainAcitivty extends Acitivity
{
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button)findViewById(R.id.btn);
        btn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View arg0) {
              Message msg=Message.obtain();
              msg.what=0x123;
              handler.sendMessage(msg);             
            }});
}
 final Handler handler=new Handler()
    {
        public void handleMessage(Message msg)
        {
           if(msg.what==0x123)
             btn.setBackgroundColor(Color.BLUE);
    }
    };
}
/*这段代码的主要实现点击一下按钮,按钮就会变蓝色,
这是通过handler来实现的,当然实际项目中肯定不是这么写,
这里只是为了讲解handler的使用才特别写的例子。*/
首先来看下Handler的构造函数:
  public Handler() {
        this(null, false);    //继续调用重载的构造函数
    }
 public Handler(Callback callback, boolean async) {
    。。。
    //省略了部分代码,着重看下面的代码
        mLooper = Looper.myLooper();`  1
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;      2
        mCallback = callback;
        mAsynchronous = async;
    }
  /*  这段代码中看到标记1那行代码,非常重点!!mLooper是Looper类型
这个代码就是给mLooper获取当前线程的Looper对象,那Looper怎么产生的呢又是什么时候产生的呢,
我们去看Looper这个类的源码的myLooper方法*/
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
/*这个地方我们看到有个sThreadLocal变量,这个是什么东西呢,
原来这个是Looper的线程局部变量,ThreadLocal,简单介绍下这个类型,
这是个线程私有变量,可以将线程内部的变量和当前线程匹配起来,
只有当前线程能够访问,这里可以简单的理解为sThreadLocal存了Looper对象,那么什么时候存的Looper对象呢,通过查找找到这么个方法*/
   private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
      /*这句是说如果调用prepare这个方法的时候,
sThreadLocal里面已经有Looper对象了那么就会抛出异常,
这就是说在每个线程中只会有一个Looper对象,这也好理解,因为Looper对象是管理队列的,而每个线程要保证只有一个队列,不然就会造成队列管理混乱呀,所以线程中只会有一个Looper对象和一个队列。所以不要重复去调用prepare()这个方法,否则要抛出异常的*/
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
        /*看到这句没,就是这里new了一个Looper对象,但是这个方法是private啊,外部是没法方位的,只有内部方法才可以访问啊,
 别急再找找 ,果然找到了,先来看看Lopper的构造函数*/
    }
    /*这个构造函数就是说了每个Looper对象生成的时候就会创建一个队列,
所以队列的创建不是在别的地方就是在Looper对象的构造函数里面的,
这也就解释了Looper管理队列的原理,线程的队列是存在Looper对象的mQueue变量里面的*/
        private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();//存了当前的线程
    }
/*下面这个方法就调用了上面 prepare(boolean quitAllowed)的方法,
继而new 了一个Loooper对象,所以在很多参考书上会在new Handler对象之前一定要有这么一句Looper.prepare(),现在懂了吧。*/
     public static void prepare() {
        prepare(true);
    }

好了这些工作做完后我们再回到刚才标记1的地方,就是mLooper = Looper.myLooper();`这句,经过前面的Looper的方法调用,我们已经生成了Looper对象和MessageQueue队列了,接下来继续看下面的代码,下面的判断里面说if(mLooper==null)就抛出异常 “Can’t create handler inside thread that has not called Looper.prepare()”这个也说了需要调用Looper.prepare(),这也验证了刚才我们走的源码流程。标记2那个地方就是将mQueue队列添加到当前这个Handler对象里面,而CallBack这里我们是没有设置那么就是null,以后写代码的时候可以自己实现这个回调函数哦。好了,这样在new一个Handler之后我们就完成了这么几个工作,在当前线程中创建了一个Looper对象和MessageQueue队列,并且将队列和Looper对象都存到当前的Handler对象里面了,这样就可以让Handler将消息发送到队列里面了。

接下来我们来看看Handler是怎么将消息发送到队列里面,Looper是怎么管理队列取消息然后再回调相应的函数呢。
我们前面说过Message是通过Handler来发送的,那么必然我们就要去看看Handler这个类里面关于发送的一些函数了
我们就以sendMessage(Message msg)这个为例子好了

public final boolean sendMessage(Message msg)
    {
    /*继续调用重载函数,根据函数名字就可以判断这是一个延迟发送的函数,只是我们没设置延迟的时间,默认就为0了*/
        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是什么东东呢,
所以这里就是我们前面说的关于Message的源头信息,
就是说我得知道这个Message是谁发的呀,
毕竟有时候Handler对象有很多对吧,
这里就是设置Message的Handler对象。
  */
        msg.target = this;
      if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        /*继续看下面的重载函数,
        其实所有的Handler向队列发送消息最后都会调用下面的队列函数,
        queue是个MessageQueue对象,去看看这个源码*/
        return queue.enqueueMessage(msg, uptimeMillis);
    }  

/*下面的代码就是在MessageQueue这个类里面的,就是queue.enqueueMessage(msg, uptimeMillis)这个方法,首先是获得自身的同步锁synchronized (this),接着这个msg跟MessageQueue实例的头结点Message进行触发时间先后的比较,
如果触发时间比现有的头结点Message前,则这个新的Message作为整个MessageQueue的头结点,如果阻塞着,则立即唤醒线程处理
如果触发时间比头结点晚,则按照触发时间先后,在消息队列中间插入这个结点
接着如果需要唤醒,则调用nativeWake函数,这个函数会经过一系列的调用最终会唤醒Looper对象的Loop方法里面取message消息*/

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;

                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

到这里Handler的发送消息完成了,那么在哪里实现了从队列取数据呢,前面我们说过Looper对象是管理队列的,那么很显然,肯定是Looper对象里面来实现取数据的。看下的代码

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;//获得Looper管理的队列,其实就是当前线程的队列

        // 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);
 /*这个地方获取到消息后就去调用target的dispatchMessage的方法,
就是发送该消息的Handler,
这里就是去执行Handler里面的方法了。
我们着重看handler里面的就行了*/

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

/*看下面的Handler的dispatchMessage这个方法 ,message的callback是个CallBack是个Runnable类型,就是说如果你设置了一个Runnable类型的对象,将它添加到Message里面了,那么Handler处理的时候就会回调改Runnable对象的run方法,举个例子说 msg=Message.obtain(Handler handler,Runnable r);这样就会执行r.run方法里面的操作了
*/

   public void dispatchMessage(Message msg) {

        if (msg.callback != null) { 
            handleCallback(msg);   1
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {  2
                    return;
                }
            }
            handleMessage(msg);     3
        }
    }

我们先来看看标记1的代码,就是去执行了Runnable对象的run方法了
private static void handleCallback(Message message) {
message.callback.run();
}
我们再来看看标记2的代码,此处的CallBack类型不是Runnable类型了,是个Handler的内部接口,看下这个内部接口的定义吧,这里有个函数,是handleMessage方法,显然如果设置了Handler的这个接口,就需要实现这个方法,什么时候设置这个CallBack接口呢,就是Handler的构造函数的时候我们那会说CallBack=null了,所以如果你要将处理消息的代码设置到CallBack这个接口里的话,就需要将这个接口在Handler的构造函数中传入就行了。

public interface Callback {
        public boolean handleMessage(Message msg);
    }    

显然我们这里是没有设置CallBack接口的,所以我么你直接看标记3这个代码handleMessage(msg);是不是很眼熟,就是我们在MainActivity里面new Handler的时候重写的handleMessage(msg)这个方法,所以到这里就会调用这个方法,而源码里面的这个方法是什么都没做,所以需要我们去具体实现相应的逻辑处理代码

  public void handleMessage(Message msg) {
    }

至此,关于Handler的整个发送消息然后消息入队列,然后从队列里面取出消息,最终在Handler里面处理消息,这个过程都已经说完了,那么前面说了在new一个Handler对象之前必须要Looper.prepare()生成当前线程的Looper对象和队列,那么我们在MainActivity里面好像没有执行这个啊,因为在ui线程启动的时候,AcitiviyThread已经帮我们生成了Looper对象和队列,也已经在Looper.loop()了,不断在读取队列的消息了,只是刚开始没消息,队列是处于阻塞状态下,所以在子线程中如果需要new一个Handler的话,必须要先Looper.prepare(),生成Looper和相应的队列,而且只能一次不能重复,然后再开启loop()这个方法不断去队列读取消息,具体例子的话我不举例了,因为是第一次写博客,所以很多语言组织上存在很多瑕疵,如果有什么建议或者错误的地方恳请大家帮忙指正,我们在后面继续更新我的博客,写一些关于Java和Android方面的知识,也是自己最近学习的心得吧,和大家一起交流交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值