源码分析异步消息处理线程机制(Looper MessageQueue Handler Message)

来源:互联网 发布:unity3d 点击物体 编辑:程序博客网 时间:2024/06/05 17:18

          异步消息处理线程:主线程创建后会创建一个Looper对象,创建Looper对象的同时会创建一个消息队列MessageQueue。然后会进入消息循环,不断轮询MessageQueue。获取到消息后会个Handler去处理,Handler也可以给MessageQueue中发送消息。当MessageQueue中没有了消息时,线程会挂起。

         那么我们根据源码分析下它的执行过程。

          线程我们应该会常常用到。我们一般使用的时候是new一个Thread跑起来,在它的run方法里执行我们要做的事情,方法执行完毕,这个线程也就结束了。而异步消息处理线程是指线程启动后会进入一个无限循环体之中,每次循环都会从其内部的消息队列中取出一个消息,并回调相应的消息处理函数,执行完一个消息后则继续循环。如果消息队列为空了,线程就会暂停,直到消息队列中有新的消息。一般来说当满足以下两点是就需要使用一个异步消息处理线程:

          1:当我们要处理的任务需要常驻(比如我们手机界面里随时可以处理用户的交互)

          2:任务需要根据外部传递的消息做不同的操作时,我们就需要使用异步消息处理线程来进行处理。

          那么要实现异步消息线程要解决几个问题:

          1:每个异步消息线程内部包含一个消息队列(MessageQueue),,队列中的消息一般采用排队机制,即先到达的消息会先得到处理。

          2:线程的执行体中使用while(true)进行无限循环,循环体从消息队列中取出消息,并根据消息的来源,回调相应的消息处理函数。

          3:其它外部线程可以向本线程的消息队列中发送消息,消息队列内部的读/写操作必须进行加锁,即不能在读(写)消息队列的时候又进行写(读)操作。

         异步消息线程在各大操作系统领域都使用的比较广泛。Android系统里面其实已经为我们实现了异步消息处理线程。因为我们可以看到,每一个Activity要能随时处理与用户的交互,同时在里面我们经常要在某些操作执行完成后要更新界面或者其它操作也就是根据外部消息的不同,做出不同的操作。这也满足我们上面提到的设计异步消息线程的两点。所以每当我们创建一个Activity的时候,系统自动为我们创建了一个与之对应的异步消息处理线程。既然系统自动为我们创建好了异步消息处理线程,那么它是如何解决上面几点问题的呢?

1:找到代码入口

         对于系统已经帮我们实现好了的东西,我们想了解其原理的话,最好的方式是去看下其源码执行过程。其实在app启动的时候会自动创建一个UI主线程,其对应的源码在ActivityThread.java这个类里面。这个类可以在sdk源码目录下搜索到。大家可以下载一个Source Insight的工具,这是一个源码查看工具。这里我把Android 6.0的源码导入该工具,然后可以使用该工具方便的查看搜索到源码。该工具具体用法这里不做讲解。刚说到UI主线程对应的是ActivityThread.java这个文件,在其里面可以搜索到public static void main(String[] args)。我们都知道main函数时作为程序的入口的,其实程序的入口也就是该Main函数。

 public static void main(String[] args) {        .....  //省略部分代码        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        if (sMainThreadHandler == null) {            sMainThreadHandler = thread.getHandler();        }        if (false) {            Looper.myLooper().setMessageLogging(new                    LogPrinter(Log.DEBUG, "ActivityThread"));        }        // End of event ActivityThreadMain.        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        Looper.loop();        throw new RuntimeException("Main thread loop unexpectedly exited");    }}

2:Looper.prepareMainLooper()/Looper.prepare()创建Looper对象及MessageQueue

我们可以看到代码里有Looper.prepareMainLooper();这也是消息循环开始的关键。我们跟进去看看。

    public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }
    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));    }
    private Looper(boolean quitAllowed) {  //Looper的构造函数:        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }
从上面代码我们可以看出,prepare(boolean quitAllowed)里面new了一个Looper对象并保存。在prepare(boolean quitAllowed)里面我们看到会判断该线程是否已经创建了Looper对象,已经创建了的话就会抛出一个异常。因为在Looper的构造函数里面创建了一个MessageQueue,而一个异步线程里面只能有一个MessageQueue,所以也就只能有一个Looper对象

       好了,对上面做个总结。就是当App启动,创建一个UI线程ActivityThread,会执行该main函数,调用Looper.prepareMainLooper();创建了一个Looper对象,同时会创建一个MessageQueue,由于在一个异步消息线程里只能有一个消息队列,所以其内部会判断该线程里面Looper对象是否已经创建,创建了的话再创建的话就会抛出异常。所以如果你在Activity里面再次调用Looper.prepare()创建一个Looper对象的话,程序会抛出异常。

3:Looper.loop()进入消息循环
      然后回到main函数继续往下走。最后会执行到Looper.loop()就是这个函数把这个线程变成了异步消息处理线程。我们进入Looper.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();        }    }
该代码的执行流程流程如下:

1:调用myLooper()函数返回当前线程的Looper对象。
2:进入while(true)无限循环。

           a:调用MessageQueue的对象queue.next()函数取出队列的消息。注意,如果当前队列为空,则当前线程会被挂起,也就是说,next()函数内部会暂停当前线程。

           b:回调msg.target.dispatchMessage(msg);函数,完成对该消息的处理,也就是说,消息的具体处理实际上是由程序指定的。msg变量的类型是Message,msg.target的类型是Handler。

           c:每处理完该消息后,需要调用msg.recycleUnchecked()回收该Message对象占用的系统资源。因为Message类内部使用了一个数据池保存Message对象,从而避免不停地创建和删除MessageL类对象。因此每次处理完该消息后,需要将该Message对象表明为空闲状态,以便使该Message对象可以被重用。简单的说就是Message对象使用完后调用recycle,表明该对象处于空闲状态,下次还有消息来时就可以再利用该Message对象。

到此其实这个异步消息处理线程就执行起来了。那么MessageQueue是如何在工作的呢?

4:MessageQueue

消息队列采用排队方式对消息进行处理,即先到的消息会先得到处理,但如果消息指定了被处理的时刻,则必须等到该时刻才能处理该消息。消息在MessageQueue中使用Message类表示,队列中的消息以链表的结构进行保存,Message对象内部包含一个next变量,该变量指向下一个消息。MessageQueue中的两个主要函数是“取出消息”和“添加消息”,分别为next()和enqueueMessage()

首先来看取出消息的函数next()该函数的内部流程分三步:

  Message next() {        // Return here if the message loop has already quit and been disposed.        // This can happen if the application tries to restart a looper after quit        // which is not supported.        final long ptr = mPtr;        if (ptr == 0) {            return null;        }        int pendingIdleHandlerCount = -1; // -1 only during first iteration        int nextPollTimeoutMillis = 0;        for (;;) {            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                // Try to retrieve the next message.  Return if found.                final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages;                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                if (msg != null) {                    if (now < msg.when) {                        // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        // Got a message.                        mBlocked = false;                        if (prevMsg != null) {                            prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);                        msg.markInUse();                        return msg;                    }                } else {                    // No more messages.                    nextPollTimeoutMillis = -1;                }                // Process the quit message now that all pending messages have been handled.                if (mQuitting) {                    dispose();                    return null;                }                // If first time idle, then get the number of idlers to run.                // Idle handles only run if the queue is empty or if the first message                // in the queue (possibly a barrier) is due to be handled in the future.                if (pendingIdleHandlerCount < 0                        && (mMessages == null || now < mMessages.when)) {                    pendingIdleHandlerCount = mIdleHandlers.size();                }                if (pendingIdleHandlerCount <= 0) {                    // No idle handlers to run.  Loop and wait some more.                    mBlocked = true;                    continue;                }                if (mPendingIdleHandlers == null) {                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];                }                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);            }            // Run the idle handlers.            // We only ever reach this code block during the first iteration.            for (int i = 0; i < pendingIdleHandlerCount; i++) {                final IdleHandler idler = mPendingIdleHandlers[i];                mPendingIdleHandlers[i] = null; // release the reference to the handler                boolean keep = false;                try {                    keep = idler.queueIdle();                } catch (Throwable t) {                    Log.wtf(TAG, "IdleHandler threw exception", t);                }                if (!keep) {                    synchronized (this) {                        mIdleHandlers.remove(idler);                    }                }            }            // Reset the idle handler count to 0 so we do not run them again.            pendingIdleHandlerCount = 0;            // While calling an idle handler, a new message could have been delivered            // so go back and look again for a pending message without waiting.            nextPollTimeoutMillis = 0;        }    }
1:调用nativePollOnce(ptr, nextPollTimeoutMillis)。这是个JNI函数,其作用是从消息队列中取出一个消息。MessageQueue类内部本身并没有保存消息队列,真正的消息队列数据保存在JNI中的C代码中,也就是说,在C环境中创建了一个NativeMessageQueue数据对象,着就是nativePollOnce()第一个参数的意义。它是一个int型变量,在C环境中,该变量将被强制转换为一个NativeMessageQueue对象。在C环境中,如果消息队列中没有消息,将导致当前线程被挂起(wait);如果消息队列中有消息,则C代码中将该消息赋值给Java环境中的mMessages变量。

2:接下来这段代码被包含在synchronized (this)关键字中,this被用做取消息和写消息的锁。本步代码比较简单,仅仅是判断消息所指定的执行时间是否到了。如果到了,就返回该消息,并将mMessages变量置空(msg.next = null;);如果时间还没有到则尝试读取下一个消息。
3:如果mMessages为空,则说明C环境中的消息队列没有可执行的消息了。因此,执行mPendingIdleHandlers列表中的"空闲回调函数"。程序员可以向MessageQueue中注册一些"空闲回调函数",从而当前线程中没有消息可处理时去执行这些"空闲代码"。

再看添加消息enqueueMessage

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

在enqueueMessage()函数中也使用了synchronized (this),其内部主要是将参数赋值给mMessages,还一个是调用nativeWake(mPtr)函数。这是个JNI函数,其内部会将mMessage消息添加到C环境中的消息队列中,并且如果消息线程正处于挂起( wait)状态,则唤醒该线程。

上面是对消息队列的读取大概讲解。主要是通过next()和enqueueMessage()两个函数。虽然消息队列提供了读取消息的函数,但是对程序员而言,一般不直接读/写消息队列。那么是如何操作的呢?

5:Handler

通过Handler向消息队列添加消息。可以调用其sendXXXX系列的函数。它们最终都会调用到MessageQueue里面的enqueueMessage()函数,往消息队列中添加消息。

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

通过Handler处理取出的消息。重载Handler类的handleMessage()函数添加消息处理代码。前面说过,在Looper.loop()函数中,当取出消息后,会回调msg.target对象(msg.target的类型是Handler)的handleMessage()函数。

Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {}};
Handler只能添加到有消息队列的线程中,否则会抛出异常。从其一个构造函数可以看出。

    public Handler(Callback callback, boolean async) {        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 = callback;        mAsynchronous = async;    } 

因此,在构造Handler对象之前,必须已经执行过Looper.prepare(),但prepare不能执行两次。


6:Message

从其字面意思可以看出为消息,其作用是和Handler一起工作达到读/取消息队列的功能。在一个线程中可以包含多个Handler对象。在Looper.loop()函数中,不同的Message对应不同的Handler,从而回调不同的handleMessage()函数。也就是不同的Handler对应不同的Message,Message里面会保存该Handler的对象。那么Message是如何与Handler关联起来的呢?我们一般发送消息按如下方式:

Message msg = mHandler.obtainMessage(int what, int arg1, int arg2);  //创建一个消息mHandler.sendMessage(msg);//通过handler发送消息
我们看Handler类里面的obtainMessage源码:

    public final Message obtainMessage(int what, int arg1, int arg2)    {        return Message.obtain(this, what, arg1, arg2);    }
    public static Message obtain(Handler h, int what, int arg1, int arg2) {        Message m = obtain();        m.target = h;        m.what = what;        m.arg1 = arg1;        m.arg2 = arg2;        return m;    }

    public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                sPoolSize--;                return m;            }        }        return new Message();    }
最终在Handler类里面的obtain函数里面调用调用Message m = obtain()创建了一个Message。创建消息的时候会去线程池里面取,避免重复创建Message。然后再通过m.target = h;把handler对象存入Message,这样Handler也就与这个Message对应起来了。所以在loop()里面通过msg.target.dispatchMessage(msg);就会回调对应handler的handleMessage()函数。也就是在有消息循环的线程里新建了一个Handler,当想要发送消息的时候通过handler的obtainMessage()函数创建了一个Message,创建的时候handler和Message进行了关联,该Message里面保存了该handler对象。在loop()里面轮询到消息时会通过该Message调用其保存的handler对象的handleMessage()函数,然后再handleMessage()函数里面对消息进行处理。


以上就是整个异步消息处理线程的工作机制,那么来再总结一遍:

1:每当App启动的时候,在其对应的UI主线程ActivityThread.java main函数里面会调用Looper.prepareMainLooper()最终调用其里面的prepare()创建Looper对象,在线程里只能调用一次Looper.prepare()。因为创建Looper对象的同时会创建一个MessageQueue,而一个异步消息处理线程里面只允许有一个MessageQueue,所以当代码内部有作判断,Looper对象已存在再创建的话会抛出一个异常。

2:创建好Looper对象和MessageQueue后,再调用Looper.loop()进入一个无限的消息循环。在MessageQueue内部有next()读取消息 和 enqueueMessage()添加消息两个函数。在loop()循环体中,调用next()读取消息。若消息队列为空,则会暂停当前线程。

3:程序员一般不直接通过MessageQueue的那两个函数去取出消息队列的消息或者添加消息到消息队列,而是通过Handler,在一个线程中可以创建多个Handler,每个Hanlder对应一个Message,Message内部保存了该Handler对象,在loop()循环体当中,循环到消息时会通过Message对象,调用与之对应的Handler对象,并回调其handleMessage()函数。Hanlder也可以往消息队列中发消息。

4:一个普通线程,若要变成异步消息处理线程,需要在线程里调用Looper.prepare() 创建Looper对象,再调用Looper.loop()进入消息循环。

0 0
原创粉丝点击