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()方法。

时间: 04-04

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

android线程消息传递机制——Looper,Handler,Message

android线程消息传递机制——Looper,Handler,Message 在引入这些概念之前,我们先了解一下引入这些机制的背景. 出于性能优化的考虑,Android的UI操作并不是线程安全的(如果你不懂什么是线程安全,可以阅读一下<一起探究多进程与多线程>里的数据安全与可重入),这意味着如果有多个线程同时操作某个UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件.这个UI线程也通常被我们称为主线程. 在此引

Android的消息处理机制——Looper,Handler和Message浅析

题外话: 说来有些惭愧,对于这三者的初步认识居然是在背面试题的时候.那个时候自己接触Android的时间还不长,学习的书籍也就是比较适合入门的<疯狂Android讲义>,当然在学到Handler这一部分的时候,书中也是有提到一些简单示例,后来在工作中需要用到这个MessageQueue的时候才开始真正琢磨了一下这三者的联系.如果想要对这三者好好理解一番,个人还是比较推荐<深入理解Android卷Ⅰ>.以下对这三者之间的恩怨纠葛的介绍和分析也是参考这本书的相关章节,算是一篇读书笔记吧

Android异步消息处理机制(1)Handler基本使用

Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.解决的方案应该是创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程.简单的说就是在子线程中实现更新UI操作. Handler基本使用 关于Handler的使用包括两种: 从子线程(worker线程)中向主线程(UI线程)发送消息,在

[Android] 异步消息处理机制(Handler 、 Looper 、MessageQueue)源码解析

1.Handler的由来 当程序第一次启动的时候,Android会同时启动一条主线程( Main Thread)来负责处理与UI相关的事件,我们叫做UI线程. Android的UI操作并不是线程安全的(出于性能优化考虑),意味着如果多个线程并发操作UI线程,可能导致线程安全问题. 为了解决Android应用多线程问题-Android平台只允许UI线程修改Activity里的UI组建,就会导致新启动的线程无法改变界面组建的属性值. 简单的说:当主线程队列处理一个消息超过5秒,android 就会抛

Android异步消息处理机制(4)AsyncTask源码解析

上一章我们学习了抽象类AsyncTask的基本使用(地址:http://blog.csdn.net/wangyongge85/article/details/47988569),下面我将以问答的方法分析AsyncTask源码内容,源码版本为:API22. 1. 为什么必须在UI线程实例化我们的AsyncTask,并且必须在主线程中调用execute(Params... params)? 在分析为什么在UI线程调用之前,我们先看一下实例化AsyncTask并调用execute(Params...

Android多线程----异步消息处理机制之Handler详解

关于Android的多线程知识,请参考本人之前的一篇博客:Android 多线程----AsyncTask异步任务详解 在Android当中,提供了异步消息处理机制的两种方式来解决线程之间的通信问题,一种是今天要讲的Handler的机制,还有一种就是之前讲过的 AsyncTask 机制. 一.handler的引入: 我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创

[学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起

Android异步消息处理机制详解及源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 最近相对来说比较闲,加上养病,所以没事干就撸些自己之前的知识点为博客,方便自己也方便别人. 1 背景 之所以选择这个知识点来分析有以下几个原因: 逛GitHub时发现关注的isuss中有人不停的在讨论Android中的Looper , Handler , Me

Android异步消息处理机制(2)源码解析

上一章讲解了Android异步消息处理机制的基本使用,下面将简单地探寻一下异步机制背后的奥妙,源码版本为:API22. 首先,声明一下本文是在我参考了一下各位大神的文章之后才慢慢熟悉的, 若有不足之处,还望各位批评指正!.菜鸟上路,,,, 郭霖博客 鸿洋博客 刘超 深入解析android5.0系统 任玉刚博客 先后顺序按照拼音排序,无关技术本身. 先简单地总结一下Looper,MessageQueue,Message和Handler四者之间的关系: Looper和MessageQueue Loo