Android面试相关

Android Handler 消息通信机制

2018-05-06  本文已影响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机制
上一篇下一篇

猜你喜欢

热点阅读