Handler消息机制原理分析-Java层

转载请注明出处:https://blog.csdn.net/Main_Stage/article/details/81134692

一、概论

Android APP开发中经常用到Handler发送消息进行处理,本篇文章从使用方法->源码分析的角度来分析Handler的消息处理机制,作为自己对Handler的总结性文章,以便日后查阅,有些图片来源于其他博主,若有侵权请告诉我。本文内容若有不足或错误的地方,请指出,多多交流。

二、模型图

Handler消息处理模型如下图:
这里写图片描述
模型的解释:以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中,Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理,在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。那么,这些过程是怎么进行的?会涉及哪些方法的调用?本文后面会进行介绍。
这里从图中可以看到参与消息处理有四个对象,它们分别是Handler, Message, MessageQueue和 Looper,它们的源码都在android.os包下面,下面是它们的类定义:

Handler public class Handler{}
Message public final class MessageQueue{}
public final class Message implements Parcelable{}
Looper  public final class Looper{}

其中MessageQueue,Message,Looper都是final类,即不能被继承扩展或覆写,Handler类是可以被继承的,这也是大家经常做的事情,这四个对象的职责如下:
Handler:消息的发送者和处理者,一个线程可以有很多个Handler。
Message:消息体,作为发送和接收的最小单元。
MessageQueue:消息队列,用于存放发出的消息,内部数据结构是链表,会根据when属性来调整消息队列的排序。一个线程只能有一个MessageQueue。
Looper:消息的遍历者,如果消息队列里有消息,就会触发遍历,然后进行分发处理的操作。

三、基本用法

Handler的使用一定要知道消息是要发送到哪个线程的消息队列,目标线程有没有Looper和MessageQueue。Android的主线程(即UI线程)是默认设置了Looper和MessageQueue的,而自己创建的子线程是没有的,需要手动调用Looper.prepare()去初始化和设置,并且在handler发送消息之后手动调用Looper.loop()触发消息循环。这就是在主线程中使用Handler与子线程中使用的区别,这点很重要。下面用代码片段的形式进行演示:

在UI线程中使用Handler:

public class HandlerActivity extends Activity {
    private MyHandler mHandler;
    private Button mBut ;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mBut = findViewById(R.id.but_handler);
        mBut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mHandler = new MyHandler();
                mHandler.sendEmptyMessage(0);
            }
        });
    }

    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("MyHandler","MyHandler--handleMessage ,msg.what=" + msg.what);
        }
    }
}

当点击按钮时发出一条空消息,然后在handleMessage中获取该消息并打印输出:
输出:D/MyHandler: MyHandler–handleMessage ,msg.what=0

在子线程中使用Handler:
在上面的例子中稍做一下改动,在子线程中初始化Handler并处理,并且手动调用Looper.prepare()和Looper.loop() ,点击按钮启动子线程,在run方法中发送消息。

public class HandlerActivity extends Activity {
    private Button mBut ;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mBut = findViewById(R.id.but_handler);
        mBut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new MyThread().start();
            }
        });
    }

    class MyThread extends Thread{
        private MyHandler mHandler ;
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new MyHandler();
            mHandler.sendEmptyMessage(0);
            Looper.loop();
        }
        class MyHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("MyHandler","MyHandler--handleMessage ,msg.what=" + msg.what);
            }
        }
    }
}

输出:D/MyHandler: MyHandler–handleMessage ,msg.what=0

如果不手动调用Looper.prepare()会报如下错误:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

如果不手动调用Looper.loop(),虽然消息会发送给MessageQueue,但是不会触发Looper去遍历消息队列,也就不会分发给对应的Handler处理。所以,发送消息的代码段一定要包含在Looper.loop()之前,发出的消息才会被分发。

在子线程中更新UI
Handler的另外一个常用的用法就是异步操作更新UI,比如子线程在做耗时任务,需要上报给UI并展示状态。我们知道在子线程中是不能直接更新UI的,但是子线程可以通过Handler给主线程发消息,让主线程更新。大家有没有想过为什么?这是巧妙地运用了子线程将消息发给主线程的消息队列,而主线程中的Handler可以更新UI。我们看下面的例子:

public class HandlerActivity extends Activity {
    private MyHandler mHandler;
    private Button mBut;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mBut = findViewById(R.id.but_handler);
        mHandler = new MyHandler();
        mBut.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new MyThread().start();
            }
        });
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mBut.setText("来自子线程的消息更新");
        }
    }

    class MyThread extends Thread {
        @Override
        public void run() {
            mHandler.sendEmptyMessage(0);
        }
    }
}

在这个代码中,Handler的初始化需要放在主线程中,Handler就可以使用主线程的Looper和MessageQueue。

四、源码分析

上一节,通过代码实现了在主线程和子线程中使用Handler的示例,以及子线程通过Handler更新UI的示例。本节主要从源代码的角度跟踪整个消息的处理流程。
大体的流程如下:
sendMessage->enqueueMessage->loop->next->dispatchMessage->handleMessage

4.1 Handler
SendMessage() :发送消息
这里写图片描述

public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)

发送消息的方法很多,它们相互调用和对Message进行包裹,最终调用到enqueueMessage方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);    
}

作用是将消息放入MessageQueue里面,到此Handler就完成了消息的发送。

dispatchMessage() :消息的分发和处理

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

private static void handleCallback(Message message) {
    message.callback.run();
}

从代码可以看出分发消息的优先级如下,并且只会调用其中的某一个回调:

msg.callback.run() > mCallback.handleMessage() > handleMessage()

msg.callback.run():当我们用post(runnable)去发送消息后,处理消息时会调用runnable的run方法,注意,调用run()不会开线程哦,这里只是用了Runnable这个特殊接口而已。
mCallback.handleMessage():如果给Handler设置了回调接口,那么会调用设置的接口。
handleMessage():最后才是我们常用的重载handleMessage方法来接收消息。

4.2 Message
由于Message给我们提供了obtain()方法用于获得Message对象,通过查看Message的源码可以发现有消息池的概念,它将已经使用过的Message对象通过recycle()重置并且放入消息池中,供obtain()复用。所以,获取Message时尽量不去new Message,而是调用Handler.obtainMessage()来得到Message对象。接下来看看这两个方法的实现:

obtain() : 获取消息对象

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

首先去复用消息池中的消息体,如果没有消息池,则重新创建消息并返回。

recycle() :重置Message,放入消息池

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    //重置Message的属性并放入消息池
    recycleUnchecked();
}

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        //可以看出,消息池里面的消息也是链表形式
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

可以看出recycle()不是类似于gc()让系统回收,而是将Message的所有属性都回归默认值,然后放入消息池中以便复用。

4.3 MessageQueue
MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,其中MessageQueue类中涉及的native方法如下:

private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

这些Native方法这里暂时不介绍,对于Handler Native层的讲解,可以查看这篇文章:http://gityuan.com/2015/12/27/handler-message-native/

这里继续追踪MessageQueue中的enqueueMessage方法,主要的作用就是将消息加入队列。

enqueueMessage() :将消息加入队列

boolean enqueueMessage(Message msg, long when) {
    //每一个msg必须有一个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) {
        //正在退出时,回收msg,加入消息池
        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…else判断消息顺序并插入队列
        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;
}

next() :提取下一个消息

Message next() {
    final long ptr = mPtr;
    //当消息循环已经退出,则直接返回
    if (ptr == 0) { 
        return null;
    }
    // 循环迭代的首次为-1
    int pendingIdleHandlerCount = -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;   
                }
            } else {
                //没有消息
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //当消息队列为空,或者是消息队列的第一个消息时
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //没有idle handlers 需要运行,则循环并等待。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            //去掉handler的引用
            mPendingIdleHandlers[i] = null; 
            boolean keep = false;
            try {
                //idle时执行的方法
                keep = idler.queueIdle();  
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置idle handler个数为0,以保证不会再次重复运行
        pendingIdleHandlerCount = 0;
        //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
        nextPollTimeoutMillis = 0;
    }
}

4.4 Looper
Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象,Looper的构造方法中会创建一个MessageQueue对象,再将Looper对象保存到当前线程TLS。

Looper.prepare() :为线程设置Looper和MessageQueue

public static void prepare() {
    prepare(true);
}

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

这里的sThreadLocal是LocalThread,ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。TLS常用的操作方法:

ThreadLocal.set(T value):将value存储到当前线程的TLS区域,源码如下:

public void set(T value) {
    //获取当前线程
    Thread currentThread = Thread.currentThread(); 
    //查找当前线程的本地储存区
    Values values = values(currentThread); 
    if (values == null) {
        //当线程本地存储区,尚未存储该线程相关信息时,则创建Values对象
        values = initializeValues(currentThread);
    }
    //保存数据value到当前线程this
    values.put(this, value);
}

ThreadLocal.get():获取当前线程TLS区域的数据,源码如下:

public T get() {
    //获取当前线程
    Thread currentThread = Thread.currentThread(); 
    //查找当前线程的本地储存区
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            //返回当前线程储存区中的数据
            return (T) table[index + 1]; 
        }
    } else {
        //创建Values对象
        values = initializeValues(currentThread);
    }
    //从目标线程存储区没有查询是则返回null
    return (T) values.getAfterMiss(this); 
}

Loop() :开始消息循环

public static void loop() {
    //获取TLS的Looper对象
    final Looper me = myLooper();
    //如果没有指定Looper则会报错,就如同之前的示例,如果不在子线程中调用Looper.prepare()就会报这个错误。
    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(); 
        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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            //很关键!让对应的Handler来分发这个消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

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

到这里,消息的分发和处理的流程已经走完了。

五,总结

消息机制是Android系统中比较重要的概念,本文从示例Demo代码片段和源码分析的角度分析Handler消息处理机制,以及跟相关类Message,MessageQueue,Looper之间的调用关系,如文章开头的原理图片。
1)我们在使用Handler的过程中一定要清楚附着的线程是否有Looper和MessageQueue,UI线程在初始化时已经通过ActivityThread设置了Looper和MessageQueue,并且Looper的生命周期跟UI线程一致,但是在子线程中必须要手动去调用Looper.prepare()去为线程初始化Looper和MessageQueue ,并且在发送消息之后手动调用Looper.loop()触发消息循环。
2)在初始化Message时尽量使用Handler.obtainMessage(),考虑性能和资源的复用会首先从消息池中获取闲置的消息对象并返回,MessageQueue的内部实现是基于链表,根据时间when进行排序,Looper每次从队列头获取消息并进入分发流程。
3)在Handler的dispatchMessage()中规定了分发消息的优先级(msg.callback.run() > mCallback.handleMessage() > handleMessage())。

你可能感兴趣的:(Android基础)