Android 异步消息处理机制Looper、Handler、Message三者关系

Looper,Handler,Message三者是我们常常用来再子线程跟新UI的,我们把Message发给Handler,然后,handler调用HandlerMessage()方法,我们在这个方法里面更新UI。那么Looper呢,又是什么,下面我来给大家介绍一下三者的关系。

首先要说明的是,每个线程最多只有一个Looper,在线程里面调用Looper.prepare()就是为这个线程设置了一个Looper,所以在子线程,我们使用这个三者前,一定要调用Looper.prepare()方法,而主线程其实已经替我们调用过这个方法了,所以我们不必重复调用。

另外我们还要主动调用Looper.loop()方法,下面说一下为什么。

Looper里面包含一个MessageQueue,这个就是一个消息队列,handler发送的消息Message都会到这个队列里面来,Looper.loop()不断从MessageQueue取出Message,没有则阻塞,有则调用Message.target的dispatchMessage(msg)方法,这个方法Message也传了过去。dispatchMessage(msg)方法里面又调用handleMessage()方法,这方法就是我们的具体实现。

那么通过刚才的描述也很清楚了,Message.target其实就是Handler。


OK,那么我再来总结一下三者之间的引用关系

Looper:拥有一个MessageQueue,里面包含所有Message

Message:每个Message都持有一个Handler引用

Handler:handler持有Looper

也就是说任意一个可以获取其他两个的引用,从而相互关联。


前面的可能表达得不是很好,下面通过源码来说明

先看Looper的静态方法prepare()

public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
这个方法是我们必须要主动调用的,sThreadLocal是一个java类库提供的一个类,用于不同线程中维护同一个变量。这个比较难解释,也就是说一个变量,本来应该是多个线程共享的,所以我们多线程操作的时候,要考虑同步的问题,但是使用了ThreadLocal以后,系统为每个线程分配一个空间来存储这个变量,也就是说每个线程只要维护好自己的变量就可以了,这个变量也就不是共享的了,从而避免同步的问题。

我们可以看到,prepare()通过sThreadLocal.get()方法,只需要存在一个Looper实例,如果重复prepare()就会抛出异常(类似单例模式,不同的是重复创建的结果是抛出异常)。

然后如果没有Looper实例,就去new一个,并且保存在sThreadLocal中。

下面看new Looper()方法

private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }
可以看到这个构造方法是私有的(所以说像单例模式),并且创建了一个消息队列mQueue,还保存了创建这个Looper的线程的引用(每个线程都只有一个Looper)

prepare()以后,我们就要主动调用静态的loop()方法,从mQueue消息队列中不断取出消息了

下面看loop()方法

public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        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();
        
        while (true) {
            Message msg = queue.next(); // might block
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }

                long wallStart = 0;
                long threadStart = 0;

                // 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);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }

                msg.target.dispatchMessage(msg);

                if (logging != null) {
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;

                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    }
                }

                // 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.recycle();
            }
        }
    }
myLooper()方法就是一个get方法,用于获取looper对象,前几行代码说明没有prepare(),是不可以调用loop()的

然后调用进入了一个死循环,queue.next()不断从队列中获取message对象,一旦获取到messgae,就调用msg.target.dispatchMessage(msg)方法。

ok,loop()基本上就做了这些事情,那么有几个疑问,message是什么时候添加到queue里面的,然后msg.target对象是什么,dispatchMessage(msg)方法又做了什么呢。


接下来我们看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;
    }

从构造函数可以看出,handler获取了Looper对象的引用,并且获取了mQeueu消息队列的引用

那么我们这么把msg添加到队列中的呢,一般我们都是调用handler的sendMessage()方法来发送消息的,但是这些方法最后都要调用同一个方法,sendMessageAtTime

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

在这个方法我们可以看到,我们设置msg的target为handler本身,然后把msg加入queue消息队列。

原来我们sendMessage的时候就把msg放到消息队列里面了。


最后我们再看一遍Loop()方法里面的

msg.target.dispatchMessage(msg);
也就是说调用了handler的dispatchMessage()方法,所以总的来说是这样的,handler把msg加入队列,loop不断取出msg,然后调用handler的dispatchMessage()方法去处理msg

接着看dispatchMessage()方法

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

这个方法里面,我们调用了handleMessage方法,其实就是一个回调,这个方法里面,我自己写对msg的处理


从上面源码总结基本上可以理清楚Looper、Handler、Message三者关系的关系了,另外再提一下在子线程中进行UI操作的其他方法:

1. Handler的post()方法

2. View的post()方法

3. Activity的runOnUiThread()方法

我们先来看下Handler中的post()方法,代码如下所示:
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:

private final Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

注意,这里讲callback设置为runnable对象里面,还记得我们的dispatchMessage()方法吗

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
里面首先对callback是否为空进行了判断,不为空,要调用handleCallback()

private final void handleCallback(Message message) {
        message.callback.run();
    }
这里主动调用了runnable的run()方法,所以这不是开启子线程的操作哦!(注意,主动调用run()方法是不会开启线程的)


然后再来看一下View中的post()方法,代码如下所示:

public boolean post(Runnable action) {
    Handler handler;
    if (mAttachInfo != null) {
        handler = mAttachInfo.mHandler;
    } else {
        ViewRoot.getRunQueue().post(action);
        return true;
    }
    return handler.post(action);
}

原来就是调用了Handler中的post()方法

最后再来看一下Activity中的runOnUiThread()方法,代码如下所示:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。