Android消息处理机制(Handler)

概述

Android消息处理机制涉及到四个类:Handler,Looper,MessageQueue,Message,显然要了解Android消息处理机制必须要知道它们都扮演着什么角色以及知道它们是如何相互配合的?

作用

Android消息处理机制可以实现线程间通信。

  1. 在Android中常见方式是工作线程发送一个消息到主线程更新UI。
  2. 在Toast和NotificationManagerService进行IPC的过程中,当NMS处理Toast的显示和隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中。

源码解析

想要了解Handler,Looper,MessageQueue,Message分别扮演什么角色,最直接的是阅读它们的代码实现,从而得到相关结论。

1.Looper

Class used to run a message loop for a thread. Threads by default do* not have a message loop associated with them; to create one, call* {@link #prepare} in the thread that is to run the loop, and then* {@link #loop} to have it process messages until the loop is stopped.

上面一段是官方对Looper的描述,大概意思是Looper被用作在一个线程中运行一个消息循环,默认情况下,线程没有与消息循环关联,想要Looper与线程关联,可以在线程中调用Looper.prepare()为运行消息循环做准备,然后调用Looper.loop()后开启消息循环并不断处理消息直到它被停止了。

现在我们了解到Looper有两个重要的方法,分别是Looper.prepare()和Looper.loop()。

首页看Looper.prepare()

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

这方法主要做了一下事情:

  1. 判断当前线程是否已经关联了Looper,如果有,则抛出异常;如果没有,那么就设置一个Looper对象到当前线程

这里我们可以得到一些结论:

  1. 一个线程只能关联一个Looper
  2. Looper对象是在这里被创建的

接着我们在看看Looper的构造函数:

private Looper(boolean quitAllowed) {    
    mQueue = new MessageQueue(quitAllowed);    
    mThread = Thread.currentThread();
}

这方法主要做了一下事情:

  1. Looper持有MessageQueue对象引用,也就是关联了一个MessageQueue。
  2. Looper持有Thread对象引用,也就是关联了一个线程。

这里我们可以得到一些信息:

  1. Looper.prepare()在那个线程被调用,Looper就与这个线程关联上了。
public static void loop() {
        //从当前线程获取Looper对象,如果为null,则抛出一个运行时异常。
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //得到与Looper绑定的MessageQueue。
        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();

        //无限循环,不断的从消息队列取数据,并分发到Handler处理
        for (;;) {
            Message msg = queue.next(); // might block
            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 traceTag = me.mTraceTag;
            if (traceTag != 0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                //在这里开始分发Message
                //msg.target其实就是Handler对象,讲到Handler源码时就会知道
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

Handler:
这方法主要做了一下事情:

  1. 判断Looper对象是否存在,不存在说明在调用此方法时,没有先调用Looper.prepare()。
  2. 进入一个无线循环状态,不断从消息队列中取消息,此外,循环终止条件是取出的消息为null。
  3. 调用msg.target实际是Handler对象的dispatchMessage方法,把消息回传到Handler。

这里得到一些疑问:

  1. msg.target是何时被赋值的?

2.Handler

我们一般使用Handler的代码形式是:

static Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //handle your message
        }
};

在这里我们有一些疑问:

  1. handleMessage方法在什么时候被调用?

先从Handler构造方法开始分析:

    public Handler(Callback callback, boolean async) {
        //这些代码是用来判断你创建的Handler是否有内存泄露的。
        //如果有则会在Logcat中打印"The following Handler class should be static or leaks might occur: xxx"
        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());
            }
        }
        
        //得到创建Handler的线程所关联的Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //Handler关联上Looper的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

一般我们发送消息是通过以下这些方法sendMessage,
sendEmptyMessageDelayed,sendMessageDelayed等等,它们最终会调用enqueueMessage,下面我们来看看代码:

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

这个方法主要做了:

  1. 为meg.target赋值为this;
  2. 调用MessageQueue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。

还记得上面遇到的疑问吗?其中有一个是:

  1. msg.target是何时被赋值的?
    显然在enqueueMessage方法中被赋值,this就是指向Handler的

到这里Handler发送消息到MessageQueue保存,在结合上面说的Looper.loop()方法,保存到MessageQueue的消息通过queue.next();被取出,然后 msg.target.dispatchMessage(msg);,又把消息分发回传到Handler中。

接下来我们接着看dispatchMessage代码

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

这里我们可以看到handleMessage被调用,也解决我们上面一个疑问:handleMessage方法在什么时候被调用?

总结

综上所述,我们可以用一句话来描述整个过程,Handler发送Message到MessageQueue中保存,Looper会不断的从MessageQueue中获取Message,然后调用与Message绑定Handler的dispatchMessage方法,最终调用会handleMessage进行真正消息处理。

结合生活的例子:就像是大跃进的时候,农民种的粮食全部上交国家的粮仓,在由国家统一分发给农民,最终还是由农民自己消费了。
Handler,Looper,MessageQueue,Message分别扮演什么角色?Handler是农民,Looper是国家,MessageQueue是粮仓,Message粮食。

自己在看源码时想到一些问题:
1.主线程可以往子线程发送消息吗?
这问题真正考验你是否真的理解Handler机制
答案是可以, Handler往那发送消息,主要是看looper类与那个线程相关联的。

2.能直接在子线程中更新UI吗?
答案也是可以的。
在类ViewRootImpl中checkThread()中检测更新UI操作是否是主线程,当ViewRootImpl还没被创建时,在子线程中更新UI是可以的。ViewRootImpl在activity生命周期的OnResume方法被调用时创建的,也就是说在执行到OnResume生命周期前,使用非UI线程是可以更新UI的。

ViewRootImpl_checkThread.png


作者:官先生Y
链接:https://www.jianshu.com/p/8bd75c1a1761
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值