美文网首页Android面试相关
Android Handler 消息通信机制

Android Handler 消息通信机制

作者: 高丕基 | 来源:发表于2018-05-06 16:53 被阅读7次

    1、概述

    Handler的消息通信机制是Android提供的一套消息异步传递机制,其主要作用是实现子线程对UI的线程的更新,实现异步消息的处理。在安卓开发中,为了保证UI操作的线程安全,Android规定只有在主线程也就是UI线程才能修改Activity里面的UI组件,同时要求在主线程中尽量不要进行耗时操作,应该尽量把耗时操作放在其他线程中进行。但往往在其他线程中处理完耗时操作得到相应结果后,需要对UI进行操作展示。这时候就出现了矛盾,Handler消息传递机制就是用来解决这个矛盾的。

    Handler处理流程

    下面是一个简单的应用实例:

    
    public class Activity extends android.app.Activity {
    
        private Handler mHandler = new Handler(){
    
            @Override
    
            public void handleMessage(Message msg) {
    
                super.handleMessage(msg);
    
                System.out.println(msg.what);
    
            }
    
        };
    
        @Override
    
        public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
    
            super.onCreate(savedInstanceState, persistentState);
    
            setContentView(R.layout.activity_main);
    
            new Thread(new Runnable() {
    
                @Override
    
                public void run() {
    
                    ...............耗时操作
    
                    Message message = Message.obtain();
    
                    message.what = 1;
    
                    mHandler.sendMessage(message);
    
                }
    
            }).start();
    
        }
    
    }
    
    

    2、模型及架构

    Handler 消息通信机制中主要包含3个部分:MessageQueue,Handler和Looper。以及一个封装消息的类Message。

    ① MessageQueue:

    消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next)。

    ② Handler:

    消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)。

    ③ Looper:

    不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。

    ④ Message

    需要传递的消息,可以传递数据。

    在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。

    架构

    每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。

    类关系

    3、源码分析

    3.1 Looper

    3.1.1 prepare()

    要想使用消息机制,首先要创建一个Looper。需要调用prepare():

    
    public static final void prepare() {
    
    //判断sThreadLocal是否为null,否则抛出异常
    
    //即Looper.prepare()方法不能被调用两次
    
    //也就是说,一个线程中只能对应一个Looper实例
    
            if (sThreadLocal.get() != null) {
    
                throw new RuntimeException("Only one Looper may be created per thread");
    
            }
    
    //sThreadLocal是一个ThreadLocal对象,用于在一个线程中存储变量
    
    //实例化Looper对象并存放在ThreadLocal
    
    //这说明Looper是存放在Thread线程里的
    
            sThreadLocal.set(new Looper(true));
    
    }
    
    //再来看下Looper的构造方法
    
    private Looper(boolean quitAllowed) {
    
    //创建了一个MessageQueue(消息队列)
    
    //这说明,当创建一个Looper实例时,会自动创建一个与之配对的MessageQueue(消息队列)
    
            mQueue = new MessageQueue(quitAllowed);
    
            mRun = true;
    
            mThread = Thread.currentThread();
    
    }
    
    

    无参情况下,默认调用prepare(true);表示的是这个Looper可以退出,而对于false的情况则表示当前Looper不可以退出。从上面代码看到,不能重复创建Looper,只能创建一个。创建Looper,并保存在ThreadLocal。ThreadLocal是线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

    3.1.2 loop()

    开启Looper需要调用loop():

    
    public static void loop() {
    
    //myLooper()方法作用是返回sThreadLocal存储的Looper实例,如果me为null,loop()则抛出异常
    
    //也就是说loop方法的执行必须在prepare方法之后运行
    
    //也就是说,消息循环必须要先在线程当中创建Looper实例
    
            final Looper me = myLooper();
    
            if (me == null) {
    
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    
            }
    
    //获取looper实例中的mQueue(消息队列)
    
            final MessageQueue queue = me.mQueue;
    
            Binder.clearCallingIdentity();
    
            final long ident = Binder.clearCallingIdentity();
    
    //进入消息循环
    
            for (;;) {
    
    //next()方法用于取出消息队列里的消息
    
    //如果取出的消息为空,则线程阻塞
    
                Message msg = queue.next(); // might block
    
                if (msg == null) {
    
                    return;
    
                }
    
                Printer logging = me.mLogging;
    
                if (logging != null) {
    
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
    
                            msg.callback + ": " + msg.what);
    
                }
    
    //消息派发:把消息派发给msg的target属性,然后用dispatchMessage方法去处理
    
    //Msg的target其实就是handler对象,下面会继续分析
    
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
    
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    
                }
    
                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();
    
            }
    
    }
    
    

    loop()进入循环模式,不断重复下面的操作,直到消息为空时退出循环,读取MessageQueue的下一条Message,把Message分发给相应的target。当next()取出下一条消息时,队列中已经没有消息时,next()会无限循环,产生阻塞。等待MessageQueue中加入消息,然后重新唤醒。主线程中不需要自己创建Looper,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。

    3.2 Handler

    3.2.1 Handler构造方法

    创建Handler:

    
    public Handler() {
    
            this(null, false);
    
    }
    
    public Handler(Callback callback, boolean async) {
    
            if (FIND_POTENTIAL_LEAKS) {
    
                final Class 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());
    
                }
    
            }
    
    //通过Looper.myLooper()获取了当前线程保存的Looper实例,如果线程没有Looper实例那么会抛出异常
    
    //这说明在一个没有创建Looper的线程中是无法创建一个Handler对象的
    
    //所以说我们在子线程中创建一个Handler时首先需要创建Looper,并且开启消息循环才能够使用这个Handler。
    
            mLooper = Looper.myLooper();
    
            if (mLooper == null) {
    
                throw new RuntimeException(
    
                    "Can't create handler inside thread that has not called Looper.prepare()");
    
            }
    
    //获取了这个Looper实例中保存的MessageQueue(消息队列)
    
    //这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了
    
            mQueue = mLooper.mQueue;
    
            mCallback = callback;
    
            mAsynchronous = async;
    
        }
    
    

    对于Handler的无参构造方法,默认采用当前线程TLS中的Looper对象,并且callback回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的Looper对象。

    3.2.2 Handler 发送消息

    发送消息有几种方式,但是归根结底都是调用了sendMessageAtTime()方法。在子线程中通过Handler的post()方式或send()方式发送消息,最终都是调用了sendMessageAtTime()方法。

    ① post():

    
    public final boolean post(Runnable r)
    
        {
    
          return  sendMessageDelayed(getPostMessage(r), 0);
    
        }
    
    public final boolean postAtTime(Runnable r, long uptimeMillis)
    
        {
    
            return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    
        }
    
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    
        {
    
            return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    
        }
    
    public final boolean postDelayed(Runnable r, long delayMillis)
    
        {
    
            return sendMessageDelayed(getPostMessage(r), delayMillis);
    
        }
    
    

    ② send():

    
    public final boolean sendMessage(Message msg)
    
        {
    
            return sendMessageDelayed(msg, 0);
    
        }
    
    public final boolean sendEmptyMessage(int what)
    
        {
    
            return sendEmptyMessageDelayed(what, 0);
    
        }
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    
            Message msg = Message.obtain();
    
            msg.what = what;
    
            return sendMessageDelayed(msg, delayMillis);
    
        }
    
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    
            Message msg = Message.obtain();
    
            msg.what = what;
    
            return sendMessageAtTime(msg, uptimeMillis);
    
        }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    
        {
    
            if (delayMillis < 0) {
    
                delayMillis = 0;
    
            }
    
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    
        }
    
    

    ③ runOnUiThread(Runnable action):

    在子线程中调用该方法可以直接更新UI线程,其实也是发送消息通知主线程更新UI,最终也会调用sendMessageAtTime()方法。

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

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

    ④ sendMessageAtTime():

    所有发送消息的方法最终都调用了该方法,来看一下它的源码。

    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    
    //直接获取MessageQueue
    
            MessageQueue queue = mQueue;
    
            if (queue == null) {
    
                RuntimeException e = new RuntimeException(
    
                        this + " sendMessageAtTime() called with no mQueue");
    
                Log.w("Looper", e.getMessage(), e);
    
                return false;
    
            }
    
    //调用了enqueueMessage方法
    
            return enqueueMessage(queue, msg, uptimeMillis);
    
        }
    
    //调用sendMessage方法其实最后是调用了enqueueMessage方法
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    
    //为msg.target赋值为this,也就是把当前的handler作为msg的target属性
    
    //如果大家还记得Looper的loop()方法会取出每个msg然后执行msg.target.dispatchMessage(msg)去处理消息,其实就是派发给相应的Handler
    
            msg.target = this;
    
            if (mAsynchronous) {
    
                msg.setAsynchronous(true);
    
            }
    
    //最终调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。
    
            return queue.enqueueMessage(msg, uptimeMillis);
    
        }
    
    

    可以看到sendMessageAtTime()`方法会调用MessageQueue的enqueueMessage()方法,往消息队列中添加一个消息。

    3.2.3 Handler 处理消息

    分析looper时,其中有一句代码:

    
    msg.target.dispatchMessage(msg);
    
    

    里面的msg.target就是发送该msg的Handler ,也是同一个Handler 来处理这个消息。

    
    public void dispatchMessage(Message msg) {
    
    //一开始就会进行判断
    
    //如果msg.callback属性不为null,则执行callback回调,也就是我们的Runnable对象
    
            if (msg.callback != null) {
    
                handleCallback(msg);
    
            } else {
    
                if (mCallback != null) {
    
                    if (mCallback.handleMessage(msg)) {
    
                        return;
    
                    }
    
                }
    
                handleMessage(msg);
    
            }
    
        }
    
    

    当Message的msg.callback不为空时,则回调方法msg.callback.run();当Handler的mCallback不为空时,则回调方法mCallback.handleMessage(msg);最后调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。从源码中我们可以看到,Message的回调方法:message.callback.run(),优先级最高;Handler中Callback的回调方法:Handler.mCallback.handleMessage(msg),优先级次之;Handler的默认方法:Handler.handleMessage(msg),优先级最低。对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑。

    3.3 MessageQueue

    3.3.1 将消息插入队列

    在Handler发送消息的源码sendMessageAtTime()中有一行消息入队的代码:return enqueueMessage(queue, msg, uptimeMillis);其最后是调用了MessageQueue的enqueueMessage(Message msg,longwhen)。

    来看一下其内部源码:

    
    boolean enqueueMessage(Message msg, long when) {
    
        // 每一个Message必须有一个target
    
        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) {  //正在退出时,回收msg,加入到消息池
    
                msg.recycle();
    
                return false;
    
            }
    
            msg.markInUse();
    
            msg.when = when;
    
            Message p = mMessages;
    
            boolean needWake;
    
            if (p == null || when == 0 || when < p.when) {
    
                //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
    
                msg.next = p;
    
                mMessages = msg;
    
                needWake = mBlocked;
    
            } else {
    
                //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
    
                //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
    
                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;
    
                prev.next = msg;
    
            }
    
            if (needWake) {
    
                nativeWake(mPtr);
    
            }
    
        }
    
        return true;
    
    }
    
    

    MessageQueue是按照Message触发时间的先后顺序排列,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

    3.3.2 将消息从队列中取出

    当发送了消息后,在MessageQueue维护了消息队列,然后在Looper中通过loop()方法,不断地获取消息。上面对loop()方法进行了介绍,其中最重要的是调用了queue.next()方法,通过该方法来提取下一条信息。看一下起next()源码。

    
    Message next() {
    
        final long ptr = mPtr;
    
        if (ptr == 0) { //当消息循环已经退出,则直接返回
    
            return null;
    
        }
    
        int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
    
        int nextPollTimeoutMillis = 0;
    
        for (;;) {
    
            if (nextPollTimeoutMillis != 0) {
    
                Binder.flushPendingCommands();
    
            }
    
            //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
    
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
    
                final long now = SystemClock.uptimeMillis();
    
                Message prevMsg = null;
    
                Message msg = mMessages;
    
                if (msg != null && msg.target == null) {
    
                    //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,为空则退出循环。
    
                    do {
    
                        prevMsg = msg;
    
                        msg = msg.next;
    
                    } while (msg != null && !msg.isAsynchronous());
    
                }
    
                if (msg != null) {
    
                    if (now < msg.when) {
    
                        //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
    
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    
                    } else {
    
                        // 获取一条消息,并返回
    
                        mBlocked = false;
    
                        if (prevMsg != null) {
    
                            prevMsg.next = msg.next;
    
                        } else {
    
                            mMessages = msg.next;
    
                        }
    
                        msg.next = null;
    
                        //设置消息的使用状态,即flags |= FLAG_IN_USE
    
                        msg.markInUse();
    
                        return msg;  //成功地获取MessageQueue中的下一条即将要执行的消息
    
                    }
    
                } else {
    
                    //没有消息
    
                    nextPollTimeoutMillis = -1;
    
                }
    
            //消息正在退出,返回null
    
                if (mQuitting) {
    
                    dispose();
    
                    return null;
    
                }
    
                ...............................
    
        }
    
    }
    
    

    nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长;当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。可以看出next()方法根据消息的触发时间,获取下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作来阻塞线程,直到有新消息到来。

    4、总结

    整个Handler 消息通信机制可用如下图来理解:

    Handler机制

    相关文章

      网友评论

        本文标题:Android Handler 消息通信机制

        本文链接:https://www.haomeiwen.com/subject/fcpurftx.html