Android异步消息处理机制分析(图文)

异步处理机制的作用

提到Android异步处理机制,我们首先会想到Handler,而大多数Android初学者对于Handler的作用仅局限于子线程如何更新UI这一方面。其实Handler能做的事情远不止于此,它贯穿于Android应用的整个生命周期,如果没有Handler,我们编写的应用就无法正常运行。总的来说,它的作用体现在两大方面:

  1. 处理延时任务(告诉app在将来的某个时间执行某个任务)
  2. 线程之间通信(子线程更新UI就是其中的一个应用)

角色分析与流程描述

与Android异步处理机制相关较为重要的类有以下6个:

  1. Handler:负责发送、处理消息,提供了send、post和handleMessage等方法
  2. Message:在线程中要传递的信息(链表结构),其中有what、arg、data等一些信息
  3. MessageQueue:消息队列,Handler发送的消息就放在队列中。
  4. Looper:负责管理消息队列,提供了loop方法不断地取出消息
  5. Thread:线程,会调用Looper的loop方法
  6. ThreadLocal:类似于HashMap,用键值对存储的一种数据结构,保证一个线程只有一个Looper

都说程序来源于生活,Android异步处理机制自然可以用生活中一个例子来说明:

在这里插入图片描述
回想我们坐火车、飞机之前过安检的流程:旅客要把自己的行李放到安检机里面,之后走到后面等待行李从机器中出来。这个例子中的旅客就相当于程序中的Handler,行李对应于Message,而安检机自然就对应于MessageQueue了。然而行李是怎么出来的呢? 是传送带送出来的,这里的传送带就相当于Looper,那么传送带为什么会动呢?因为有电动机为其提供了动力,在程序中,为Looper提供动力的就是线程了。在线程中会调用Looper的loop方法,而loop方法是一个死循环,导致消息队列中的消息源源不断地被取出来进行处理。

例子理解了之后,还有几个问题需要我们思考的。

首先,旅客不止一个所以安检机中行李也有许多,那么旅客怎么确定自己要取的到底是哪一个呢?这个问题听上去好像有点蠢,难道自己的行李自己还不认识吗?在现实生活中确实能认出来,不过在程序中怎么让Handler识别出自己发送的消息呢?针对这个问题我们可以想到,在发送的时候给消息做一个标记,标记出发送消息的Handler,那么在处理消息的时候就可以根据这个标记来找到应该要处理的Handler对象了,这一点在后面的源码分析中就会看到。

源码分析

Message

首先来看一下Message类中包含的一些重要字段:

	//用来识别消息的字段
	public int what;
	//Message中可以携带的整形数据arg1和arg2
	public int arg1;
	public int arg1;
	//Message中可携带的对象
	public Object obj;
	//记录是哪个Handler发送的消息
	Handler target;
	//指针,指向下一个Message
	Message next;

	//缓存池
	private static Message sPool;
	//当前缓存池的大小
	private static int sPoolSize = 0;
	//缓存池最大容量
	private static final int MAX_POOL_SIZE = 50;

接下来看一下它的构造方法:

	public Message() {
    }

由源码中看到构造方法中什么都没有,然而在开发中并不建议通过new的方式创建出Message对象。Message中由一个static类型的sPool字段,它代表了缓存池,也就是说当一个消息被处理掉之后并不会直接销毁,而是放在缓存池中,下次再需要时可以通过Message提供的obtain方法从池中得到,这样一来便减少了内存的开销,实现了资源的复用。

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

这个方法很简单,如果池子不为null则会取出池中第一个Message(头节点)并将sPoolSize减一,否则返回一个new出来的Message对象。

准备工作

在发送消息之前,要进行一些准备工作(初始化操作)。这一点很好理解:想坐飞机要通过安检,因此首先要有一个安检机,所以在发消息之前要确保有一个Looper对象(MessageQueue在Looper中创建)。我们来看一下Looper的构造函数:

	private Looper(boolean quitAllowed) {
		//创建出消息队列
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

诶?居然是私有的,这就意味着我们不能在外界new出Looper对象。既然构造函数被私有化,则说明Looper一定提供了一个public的方法来获取对象,这个方法就是myLooper:

	public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在方法中是通过ThreadLocal的get方法来获取到Looper对象的,那么这个对象是通过哪个方法被创建出来的呢?答案就是prepare方法了:

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

由上述代码我们发现,当调用了Looper.prepare()之后会new出一个Looper对象并存到ThreadLocal中,接下来我们点开ThreadLocal的set方法瞧瞧:

	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

简单来说,ThreadLocal跟HashMap类似,是一个键值对的存储结构,首先根据当前线程获取到Map,Map存储数据时是以ThreadLocal自身为key,传进来的参数为value。这样我们便得出了一个结论:每一个线程最多只能有一个Looper对象,而Looper中维护了一个MessageQueue对象,因此每个线程也只能有一个MessageQueue对象。这是因为在prepare方法中首先会判断,如果当前线程从ThreadLocal获取到的Looper对象不为null(说明此前已经调用过prepare方法创建Looper对象了)就会抛出异常。

发送消息

发送消息是通过Handler提供的一系列send方法完成的,那么首先要有Handler对象才能发送,所以我们先来看一下Handler的构造方法:

	public Handler(Callback callback, boolean async) {
        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 " + Thread.currentThread() + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler的构造方法中首先要通过Looper.mLooper方法获取到Looper对象,并通过Looper对象获取到MessageQueue对象给mQueue赋值。这里我们需要注意:在哪个线程创建的Handler对象,这个Handler对象中的mLooper引用指向的就是哪个线程中唯一的那个Looper对象。 如果该线程还没有Looper对象,new出Handler时就会抛出异常:Can’t create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare(),这就是上一小节准备工作的作用。

有了Handler对象之后,就可以通过它提供的一系列send、post方法来发送消息了。不管用哪一个方法来发送消息,最终都会调用Hander的enqueueMessage方法,而Hander的enqueueMessage方法中又会调用MessageQueue的enqueueMessage方法,整个过程可以用下图表示:

在这里插入图片描述
Handler的enqueueMessage方法如下所示:

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

可以看到不管是哪个方法,最终都会调用MessageQueue的enqueueMessage方法:

	boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            //如果目标为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) {
                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;
            //如果队列位null或者message的when=0或者message的when小于队列第一个元素的when
            if (p == null || when == 0 || when < p.when) {
                //此时应该将新消息插入到链表首部
                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;
                //找到p和prev,其中p为message的下一个元素,prev为message的上一个元素
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //将message插入到链表的对应位置
                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;
    }

如果对数据结构中的链表比较熟悉,那么上述代码应该不难理解。总的思路就是将要的Message按要执行的时间顺序插入到链表中:

在这里插入图片描述
这也就说明,不管调用哪个方法发送消息,最终都是向消息队列中添加这条消息。那么问题来了,应用运行中可能存在很多个线程,每一个线程都有一个消息队列,到底往哪个队列中添加消息呢

为了回答这个问题,我们要回想一下Handler中的queue是从哪里来的?在Handler的构造函数中是通过myLooper方法获得Looper对象,再通过Looper对象获取到queue的。这就说明:不管在哪个线程中通过Hander对象发送消息,最终都是向创建出Handler的线程中的MessageQueue队列中添加。

处理消息

上一小节分析了发送消息的流程,得出的结论就是每次发送消息都是将其添加到消息队列中。俗话说得好,有进就有出,那么进到队列中的消息是怎么取出并被Handler处理的呢?

MessageQueue提供了enqueueMessage方法添加消息,也提供了next方法取出一条消息:

	Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        
        //死循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

			//阻塞nextPollTimeoutMillis时间
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    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 {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else { //该消息携带的Handler对象(target)不为null
                        	//链表头节点出队
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //返回消息
                        return msg;
                    }
                } else {
                	//如果消息队列中没有消息,将nextPollTimeoutMillis置为-1,-1表示无线等待
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
            }

            ……
            
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

那么谁调用了MessageQueue的next方法呢?当然就是Looper了,Looper的loop方法如下:

	public static void loop() {
		//获取到当前线程下的Looper对象
        final Looper me = myLooper();
        if (me == null) {
        	//如果为null则抛异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取到me的消息队列
        final MessageQueue queue = me.mQueue;

        ……

		//死循环,不断地从消息队列中取出消息
        for (;;) {
        	//从队列中取出消息,若队列中没有消息,则阻塞
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ……

            try {
            	//从消息中找到对应的Handler进行处理
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
   
   			……

            msg.recycleUnchecked();
        }
    }

上述代码中会调用MessageQueue的next获取到下一条要处理的消息,之后通过msg.target.dispatchMessage(msg)语句找到对应的Handler进行处理:

	public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //没设置Callback就会调用handleMessage方法进行处理
            handleMessage(msg);
        }
    }

停止Looper

如果在线程中调用了Looper.loop,那么这个线程就会无限循环的从消息队列中取出消息,这样一来线程不会终止,资源也就不会被释放。如何停止循环呢?回顾一下loop方法中死循环的代码:

	for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        
        ……
        
    }

可以发现,如果消息队列的next方法返回值为null则会跳出循环,那么next方法什么情况返回null呢?看一下next方法中死循环中的一段代码:

	if (mQuitting) {
         dispose();
         return null;
    }

如果mQuitting变量为true则会返回null,那么什么情况会为true呢?细心一点就会发现MessageQueue提供了一个quit方法:

	void quit(boolean safe) {
        if (!mQuitAllowed) {
        	//主线程不能停止循环!
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            
			//将mQuitting置为true
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            //唤醒线程
            nativeWake(mPtr);
        }
    }

方法中将 mQuitting设为了true,这样一来就停掉了循环的过程。我们再留意一下quit的第一个if判断,里面抛出了一个异常提示Main thread not allowed to quit,也就是说只能通过quit停掉子线程中的loop循环,那么为什么主线程中的loop不能停掉呢?这个问题就要从ActicityThread的源码中获得答案了。

	public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

		//系统为主线程准备了Looper
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

		//为主线程创建Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

		//系统为主线程开启了loop循环
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在代码中我们发现,ActivityThread的主方法中调用了Looper.prepareMainLooper()和Looper.loop()方法,所以在主线程中可以直接创建Handler而不用手动做准备工作。此外,还可以看到主线程中创建了一个sMainThreadHandler,这个Handler不是之前分析过的Handler,而是ActivityThread中的一个内部类:

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int SERVICE_ARGS            = 115;
        public static final int STOP_SERVICE            = 116;

        ……

        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
                case RECEIVER:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                    handleReceiver((ReceiverData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case CREATE_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                    handleCreateService((CreateServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case BIND_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                    handleBindService((BindServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case UNBIND_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                    handleUnbindService((BindServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case SERVICE_ARGS:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
                    handleServiceArgs((ServiceArgsData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
                    handleStopService((IBinder)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case CONFIGURATION_CHANGED:
                    handleConfigurationChanged((Configuration) msg.obj);
                    break;
                case CLEAN_UP_CONTEXT:
                    ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
                    cci.context.performFinalCleanup(cci.who, cci.what);
                    break;
                    
               ……
               
        }
    }

可以看到,主线程中默认创建的Handler起到了至关重要的作用:开启、退出应用,创建服务,广播等等。所以说如果没有Handler,Android应用就无法正常运行。

阻塞和唤醒机制

消息队列的阻塞可能有两种情况,一是目前还没有到队列中第一个消息该被处理的时间,二是队列中已经没有消息了,这两种情况都是由一个nativePollOnce方法来完成的,我们来看看它的定义:

private native void nativePollOnce(long ptr, int timeoutMillis);

发现它是一个native方法,这意味着阻塞是通过Linux层来完成的,其中ptr参数代表着当前线程的指针,timeoutMillis参数代表要阻塞的时间,timeoutMillis为-1时代表一直阻塞。在处理消息一小节的源码中可以看到,如果队列中有消息就计算出当前时间距离消息要执行的时间的差值,没有就将timeoutMillis置为-1。

根据上面的分析,如果消息队列中没有消息线程就会一直阻塞,那么应该如何唤醒呢?首先我们能想到的是,如果给队列中添加一个消息,线程应该被唤醒。所以我们回顾一下MessageQueue的enqueueMessage是不是这样做的:

	boolean enqueueMessage(Message msg, long when){
		synchronized (this){
		
			……

			if (needWake) {
                nativeWake(mPtr);
            }
		}
	}	

在同步代码块的最后会调用nativeWake方法来唤醒线程,这个nativeWake方法同样是一个native方法(跟nativePollOnce对应):

private native static void nativeWake(long ptr);

除了添加消息之外,在上一小节中提到的quit方法中也会唤醒线程,这一点是必然的,如果不唤醒的话,子线程loop死循环就不会跳出,线程也就不会终止了。

Callback

回顾一下Handler中处理消息的dispatchMessage方法:

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

如果msg或者handler中的callback都为null才会执行handler自己的handleMessage方法,如果不为null就会执行它们对应的callback方法。总结一下,callback被调用的情况大致有3种:

  1. Handler的post()方法
  2. View的post()方法
  3. Activity的runOnUiThread()方法

首先看看Handler中的post()方法:

	public final boolean post(Runnable r){
        return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
	private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

post方法的本质还是调用sendMessageDelayed来发送一条消息,只不过在getPostMessage种将线程封装成了Message中的callback,其本质和发送一条正常的消息是一样的。

再来看看runOnUiThread方法:

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

如果当前线程不是主线程,就会post一条消息,否则直接在主线程种执行action的run方法。

最后看看View的post方法:

	public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

内部也是调用了Handler的post方法。

捋一下思路

目前为止异步处理的流程基本已经分析完了,我们来从大的方向上重新捋一下思路:在一个线程中想要通过Handler发送消息,首先要调用Looper.prepare来使当前线程具备一个Looper(只能有一个),在Looper的构建中会创建一个MessageQueue,这样一来每一个线程对应了唯一的Looper,也就对应唯一的MessageQueue。当创建一个Handler对象时,其内部会调用mLooper = Looper.myLooper()来让创建出的Handler对象获取到在创建线程中的Looper,再通过这个Looper来获取到该线程中的MessageQueue,示意图如下:

在这里插入图片描述

总的来说,一个线程中可以创建许多Handler,然而这些Handler对象持有的Looer、MessageQueue引用指向的都是相同的对象(线程中唯一的的)。这样一来,在其他线程中用这些Handler对象去发送消息(发出的消息持有发消息的Handler对象的引用),发出去的消息最终都是被放到了创建Handler线程中对应那个MessageQueue中。而创建Handler的线程中通过Looper.loop死循环不断地从消息队列中取出消息,这样便实现了线程之间的通信;由于对消息的入队和出队操作都是加了锁的,因此便保证了通信的安全性。

同步屏障机制

Handler 中存在着一种叫做同步屏障的机制,它可以实现异步消息优先执行的功能,为了说明这个机制,我们先要从Message消息类型说起。Message可以分为3种:同步消息、异步消息和屏障消息。我们通常使用的消息都是同步消息,而屏障消息在消息队列种就相当于一堵墙,墙下面有一个洞,这样一来在挡在墙后面的普通消息(同步消息)就无法通过(被Handler处理),异步消息可以通过。

Android 系统中的 UI 更新相关的消息属于异步消息,需要优先处理。简单说,如果在绘制UI这条消息前面有一条非常耗时的消息,那就会导致 UI 不能按时绘制,导致卡顿掉帧。同步消息屏障就可以用来保证 UI 绘制的优先性:

在这里插入图片描述
Message提供了以下方法设置和判断异步消息:

	public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }
    
	public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

什么样的消息是屏障消息呢?屏障消息的特点是携带的Handler对象target为null,MessageQueue提供了一个postSyncBarrier方法来发送一条屏障消息:

	private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

上述代码非常简单,就是按时间顺序将这条消息插入到消息队列中,值得注意的是这条消息是没有target的。如果想移除屏障,可以调用MessageQueue的removeSyncBarrier方法:

	public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

方法很简单,就是通过插入同步屏障时返回的token 来移除屏障。接着我们来回顾一下MessageQueue的next方法(保留了关键代码):

	Message next() {
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {        
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { //消息队列首部遇到屏障
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
                }
                if (msg != null) {
					……
				}
                
                ……
               
        }
    }

可以看到,在如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if下面的代码。可以看到通过这种方式就挡住了所有的普通消息。

回顾以下同步消息的发送,我们会得到以下结论:

  1. 屏障消息和非屏障消息的区别在于屏障消息没有tartget,这一点很好理解。因为屏障消息的作用仅仅是阻挡同步消息,它本身不需要被Handler处理。
  2. 屏障消息只会挡住它后面的同步消息的分发。
  3. postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  4. postSyncBarrier方法是私有的,所以通常情况下不需要我们发送屏障消息。
  5. 插入屏障消息不会唤醒消息队列所在线程,因为屏障消息不需要被处理。

常见问题的解答

(1)一个线程可以有几个Handler?

不管在哪个线程都可以通过Handler handler = new Handler()的方式创建Handler对象,只要内存够用创建多少都是可以的。

(2)为什么一个线程最多只能有一个Looper?是如何保证的?

在Looper.prepare方法的源码中是通过ThreadLocal来创建Looper对象的。其过程是:首先获取到当前的线程,再由这个线程获取到线程中的ThreadLocalMap对象,最后通过map的set方法将new出的Looper以键值对的形式存到map中。key为ThreadLocal,value为Looper,一个key只能对应一个value,而Looper中的ThreadLocal对象是由static和final修饰的,因此具有唯一性。此外,prepare方法首先会判断如果当前线程已经具有了一个Looper,那么就会抛出异常:Only one Looper may be created per thread。

(3)使用Handler时可能会发生内存泄露,原因是什么?

这个问题要从Java非静态内部类说起,非静态内部类中会隐式的持有外部类的对象,而我们通常是通过匿名内部类的方式使用Handler的,所以Handler中会持有Activity的引用。如果使用Handler发送了一条延时任务,在处理时间未到时如果Activity被销毁掉(onDestory方法执行),此时Activity对象应该从内存中被清除,然而Activity还被Handler持有引用,因此JVM不会清理掉Activity的内存。内存该释放时没有释放,即发生了内存泄漏。

具体的引用链为:MessageQueue → Message → Handler → Activity(箭头代表持有)

(4)为什么主线程可以直接new出Handler?想要在子线程中new出Handler需要做哪些操作?

根据上面的分析,在ActivityThread的源码中我们看到main方法中已经调用了Looper.prepareMainLooper()和Looper.loop()方法,所以可以在主线程中直接new出Handler。要在子线程中new出Handler,也需要做这些操作,示例代码如下:

	new Thread(new Runnable() {
        Handler myHandler;
        @Override
        public void run() {
            Looper.prepare();
            myHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //处理消息
                }
            };
            Looper.loop();
        }
    }).start();

(5)子线程中的Looper,在消息队列中没有消息时会怎么样?应该怎么处理?

由上面的源码分析得知当消息队列中没有消息时MessageQueue的next会阻塞,阻塞就会导致子线程无法被销毁。想要结束子线程释放资源,就要调用MessageQueue的quit方法,quit方法中最后会调用nativeWake方法来唤醒阻塞的线程,从而停掉死循环。

(6)在多个Handler往消息队列中添加数据时,如果发送时这些Handler处于不同线程,那么是如何保证安全性的?

根据源码我们发现MessageQueue的入队和出队方法中都有synchronized关键字进行加锁保护,而锁对象是this也就是当前MessageQueue。由于每个线程只有一个Looper因此也只有一个MessageQueue,所以在多线程条件下,它们发消息需要的锁是同一个,也就当一个线程拿到锁之后别的线程只能等待,这样便保证了在并发条件下的安全性。

(7)如何创建一个Message?原理是什么?

通常情况下当我们需要一个Message的时候是通过new的方式创建对象的,这样一来当消息的需求量比较大时就要创建好多个Message,造成大量创建、销毁对象的操作(内存抖动)。事实上Message为我们提供了一个obtain方法,之前已经分析过了,这个方法首先不会去new出Message,而是在缓存池中直接获取,即实现了资源的复用。那么池中的消息从哪里来呢?按常理推断,每次一个消息被处理完之后就应该被放到缓存池中,那么我们来看一下loop方法中有没有这样的操作是不是这样的:

	public static void loop() {
		……
		for(;;){
			Message msg = queue.next();
		
			……

			msg.recycleUnchecked()
		}

发现for循环的最后调用了消息本身的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++;
            }
        }
    }

根据上述代码我们发现,recycleUnchecked方法并没有将消息本身清除掉,而是将消息中携带的内容置为了null(清空),之后在同步代码块中将这条用过的消息存在缓存池的首部(头节点)。这样一来当我们调用obtain方法时就不用再new出消息,而是直接从池子中获取即可。

(8)Looper死循环为什么不会导致应用卡死(ANR)?

在之前的分析中提过,如果消息队列中没有消息,next方法就会阻塞(nativePollOnce导致主线程睡眠),那么为什么主线程无论睡眠多久都不会产生ANR问题呢?

想要说明这个问题就要先从为什么会产生ANR来说起,产生ANR的根本原因是5秒钟内没有响应按键、触屏等输入事件,或者10秒钟内广播接收器没有处理完广播。然而在Android中,这些输入事件都是以Message的形式存在的,说到这里其实问题就已经解决了。就这个问题的本质来说,主线程的睡眠和ANR根本没有半毛钱关系。产生ANR的前提是消息队列中有消息然而却没有在规定时间内被处理(消息阻塞),而主线程睡眠的前提是消息队列中没有消息,所以这两者是没有丝毫关系的,当主线程loop处理完所有消息之后就应该睡眠(该睡就得睡)。

举一个生活中的例子:老婆叫你去银行取100w,由于银行距离家较远所以需要走一段时间。刚走到银行老婆又让你去4s店提一辆奔驰,还没到4s店老婆又想喝奶茶让你去买。这样一来由于前面的事情还没办好后面的事情就被耽搁了,也就无法在老婆规定的时间内将奶茶送到她的手里(ANR产生的原因)。换一种情况,当你把老婆交代的所有事情做完,手头没有其他事情,这时候就可以睡觉了(主线程睡眠),睡了5分钟老婆突然想吃西瓜,她又会把你叫醒去买西瓜(有新的消息时主线程被唤醒)。根据这样的情景,难道可以说没有在规定时间买到奶茶的原因是你睡着了吗?很明显这样的说法是无理取闹的。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值