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

来源:互联网 发布:淘宝保健品怎么发布 编辑:程序博客网 时间:2024/05/24 01:49

今天对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方面的知识,也是自己最近学习的心得吧,和大家一起交流交流

0 0
原创粉丝点击