Android基础之Handler分析

607 阅读9分钟

1.Handler源码整体框架

Handler核心原理

为什么Android少有线程问题,如何实现线程间通信?

数据通信会带来什么开发中的问题?

  1. 线程间如何通讯?

Handler通信实现的方案实际上是内存共享的方案

  1. 为什么线程间不会干扰?

内存管理设计思路优秀

  1. 为什么wait/notify用武之地不大?

因为handler已经将需要这部分功能进行了Linux层的封装

Handler的整个流程

handler.sendMessage发送消息到消息队列MessageQueue,然后looper调用自己的loop()函数带动MessageQueue从而轮询messageQueue里面的每个Message,当Message达到了可以执行的时间的时候开始执行,执行后就会调用message绑定的Handler来处理消息。大致的过程如下图所示:

image-20210412083604412

handler机制就是一个传送带的运转机制。

  1. MessageQueue就像履带。
  2. Thread就像背后的动力,就是我们通信都是基于线程而来的。
  3. 传送带的滚动需要一个开关给电机通电,那么就相当于我们的loop函数,而这个loop里面的for循环就会带着不断的滚动,去轮询messageQueue
  4. Message就是我们的货物了。

子线程中做的事情:

handler->sendMessage->messasgeQueue.enqueueMessage   //消息队列队列的插入节点 

主线程中做的事情:

looper.loop()-> messasgeQueue.next()-> handler.dispatchMessage()->handler.handleMessage()

架构师眼中Handler

生产者-消费者设计模式,内存共享原理,Theadlocal原理

生产者-消费者模型:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据, 消费者从存储空间中取走数据。

image-20210412084041124

设计的优点

保证数据生产消费的顺序(通过MessageQueue,先进先出) - 不管是生产者(子线程)还是消费者(主线程) 都只依赖缓冲区(handler),生产者消费者之间不会相互持有,使他们之间没有任何耦合。

Handler分析的使命

  1. 如何实现线程间跨越的?
  2. Handler是如何管理那块共享的内存的?
  3. 架构师从中可以学到什么样的设计思维?

2.源码分析

  • Hanlder:发送和接收消息
  • Looper:用于轮询消息队列,一个线程只能有一个Looper
  • Message: 消息实体
  • MessageQueue: 消息队列用于存储消息和管理消息

Handler

Handerl的主要函数:

1.发送消息

2.dispatchMessage

image-20210412083713262

Looper

关键方法

 //1.初始化
Looper.prepare() 
 //2.开启循环
Looper.loop()   
for (;;) {
    //获取消息
    queue.next();

}
  1. Looper的初始化方法

    prepare有两个重载的方法,主要看 prepare(boolean quitAllowed)quitAllowed的作用是在创建MessageQueue时标识消息队列是否可以销毁。

//1.方式一:对当前线程的初始化,子线程要开启Looper必须调用该方法
public static void prepare() {
        prepare(true);  //消息队列可以quit 
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {  //不为空表示当前线程已经创建了Looper 
        throw new RuntimeException("Only one Looper may be created per thread");  //每个线程只能创建一个Looper 
    }
    sThreadLocal.set(new Looper(quitAllowed));  //创建Looper并设置给sThreadLocal,这样get的 时候就不会为null了
}

//2.方式二:对主线程的初始化,在ActivityThread中被调用
@Deprecated
public static void prepareMainLooper() {
    prepare(false);  //消息队列不可以quit,主线程不可被销毁
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Looper对象是一个TheadLocal ,即每一个线程只有一个Looper对象,保证Looper的唯一性。ThreadLocal线程隔离工具类:

image-20210412083733468

  1. 创建MessageQueue以及Looper与当前线程的绑定

    private Looper(boolean quitAllowed) { 
        mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue 
        mThread = Thread.currentThread(); //当前线程的绑定 
    }
    
  2. 开启循环

     public static void loop() {
            final Looper me = myLooper();   //里面调用了sThreadLocal.get()获得刚才创建的Looper对象 
            if (me == null) {  //如果Looper为空则会抛出异常 
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            if (me.mInLoop) {
                Slog.w(TAG, "Loop again would have the queued messages be executed"
                        + " before this one completed.");
            }
    
            me.mInLoop = true;
            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();
    
            // Allow overriding a threshold with a system prop. e.g.
            // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow", 0);
    
            boolean slowDeliveryDetected = false;
            //这是一个死循环,从消息队列不断的取消息
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    //由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage enqueueMessage后                
                    //队列里才有消息 
                    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);
                }
                // Make sure the observer won't change while processing a transaction.
                final Observer observer = sObserver;
    
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
    
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                Object token = null;
                if (observer != null) {
                    token = observer.messageDispatchStarting();
                }
                long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
                try {
                    ///msg.target就是绑定的Handler,详见后面Message的部分,Handler开始
                    msg.target.dispatchMessage(msg);
                    if (observer != null) {
                        observer.messageDispatched(token, msg);
                    }
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } catch (Exception exception) {
                    if (observer != null) {
                        observer.dispatchingThrewException(token, msg, exception);
                    }
                    throw exception;
                } finally {
                    ThreadLocalWorkSource.restore(origWorkSource);
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false;
                        }
                    } else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true;
                        }
                    }
                }
                if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                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();
            }
        }
    

MessageQueue

MessageQueue的主要函数

  1. MessageQueue的构造方法

    MessageQueue(boolean quitAllowed) { 
        //mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的quit()方法
        mQuitAllowed = quitAllowed;        
        mPtr = nativeInit();    
    }
    
  2. 向消息队列添加消息

    MessageQueue.enqueueMessage();
    
  3. 从消息队列中获取消息,使用for循环

    MessageQueue.next();
    

image-20210412083928599

入队: 根据时间排序,当队列满的时候队列(一个优先级队列)进入阻塞状态,直到用户通过next取出消息。当next方法被调用,通知MessagQueue可以进行消息的入队。

出队:Looper.loop()启动轮询器对queue进行轮询。当消息达到执行时间就取出来;当messageQueue为空的时候队列阻塞,等消息队列调用enqueue Message的时候通知队列可以取出消息,停止阻塞。

消息入队时的插入方法

采用插入排序的算法对优先级队列进行排序

image-20210412083857551

Message

创建Message

可以直接new Message,但是有更好的方式 Message.obtain。因为可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能的目地。

public static Message obtain(Handler h) {         
    Message m = obtain();//调用重载的obtain方法         
    m.target = h;//并绑定的创建Message对象的handler 
    return m;     
} 
 
public static Message obtain() {         
    synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全             
        if (sPool != null) {//sPool是就是handler dispatchMessage 后 通过recycleUnchecked 回 收用以复用的Message                  
             Message m = sPool;                 
             sPool = m.next;                 
             m.next = null;                 
             m.flags = 0; // clear in-use flag                 
             sPoolSize--;                
             return m;             
         }         
    }         
    return new Message();     
} 

Message和Handler的绑定

创建Message的时候可以通过 Message.obtain(Handler h) 这个构造方法绑定。当然可以在 在Handler 中的 enqueueMessage()也绑定了,所有发送Message的方法都会调用此方法入队,所以在创建Message的时候是可以不绑定的。

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

Handler发送消息

Handler发送消息的重载方法很多,但是主要只有2种。 sendMessage(Message) 方法通过一系列重载方法的调用,sendMessage调用sendMessageDelayed,继续调用sendMessageAtTime,继续调用 enqueueMessage,继续调用messageQueueenqueueMessage方法,将消息保存在了消息队列中,而最终由Looper取出,交给Handler的dispatchMessage进行处理。

我们可以看到在dispatchMessage方法中:

  1. messagecallback是一个Runnable对象,如果callback不为空,则直接调用callbackrun方法;
  2. 否则判断mCallback是否为空,mCallback在Handler构造方法中初始化;
  3. 在主线程通直接通过无参的构造方法new出来的为null,所以会直接执行后面的handleMessage()方法。
public void dispatchMessage(Message msg) {     
    if (msg.callback != null) {//1.callback在message的构造方法中初始化或者使用 handler.post(Runnable)时候才不为空         
         handleCallback(msg);     
    } else {         
         if (mCallback != null) {//2.mCallback是一个Callback对象,通过无参的构造方法创建出来的handler, 该属性为null,此段不执行             
              if (mCallback.handleMessage(msg)) {                 
                  return;             
              }         
         } 
         handleMessage(msg);//3.最终执行handleMessage方法     
    } 
} 
 
private static void handleCallback(Message message) {         
    message.callback.run();     
}

Handler处理消息 在handleMessage(Message)方法中,我们可以拿到message对象,根据不同的需求进行处理,整个Handler机制的 流程就结束了。

3.设计亮点

分享handler延迟的根本

亮点一: 享元模式

recycleUnchecked 在looper里面的调用。

如果是new message() ,会申请内存使用完之后再回收,整个过程会产生内存碎片。 如果频繁的生成对象就会产生内存抖动,也会引发OOM。Handler的message采用享元设计,目的是实现内存复用

难点一: nativePollOnce/NativeWake

image-20210412084557971

image-20210412084604872

疑惑点2: Looper什么时候退出

在子线程创建Looper经常会有内存泄漏,因为,Looper没有释放

image-20210412084208416

4.如何保证线程安全?

Handler是用于线程间通信的,但是它产生的根本并不只是用于UI处理,而更多的是handler是整个app通信的框架, 大家可以在ActivityThread里面感受到,整个App都是用它来进行线程间的协调。Handler既然这么重要,那么它的线程安全就至关重要了,那么它是如何保证自己的线程安全呢?

Handler机制里面最主要的类MessageQueue,这个类就是所有消息的存储仓库,在这个仓库中,我们如何的管理好 消息,这个就是一个关键点了。消息管理就2点:

  1. 消息入库(enqueueMessage)
  2. 消息出库(next),所以这两个接口是确保线程安全的主要档口。

enqueueMessage 源码如下:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //锁开始的地方
        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

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

synchronized锁是一个内置锁,也就是由系统控制锁的lock unlock时机的。这个锁,说明的是对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,然而在我们的Handler里 面,一个线程是对应着一个唯一的Looper对象,而Looper中又只有一个唯一的MessageQueue(这个在上文中也有介绍)。所以,我们主线程就只有一个MessageQueue对象,也就是说,所有的子线程向主线程发送消息的时候, 主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱。

另外再看next函数

 @UnsupportedAppUsage
    Message next() {
        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();
            }

            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) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

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

next函数很多同学会有疑问:

我从线程里面取消息,而且每次都是队列的头部取,那么它加锁是不是没有意义呢?答 案是否定的,我们必须要在next里面加锁,因为,这样由于synchronized(this)作用范围是所有 this正在访问的代 码块都会有保护作用,也就是它可以保证 next函数和 enqueueMessage函数能够实现互斥。这样才能真正的保证多 线程访问的时候messagequeue的有序进行。

小结: 这个地方是面试官经常问的点,而且他们会基于这个点来拓展问你多线程,所以,这个地方请大家重视。

5.消息机制之同步屏障

消息是根据执行时间进行先后排序,然后消息是保存在队列中,因而消息只能从队列的队头取出来。那么问题来了!需要紧急处理 的消息怎么办?

同步屏障,view绘制中用 juejin.cn/post/684490… 同步屏障的概念,在Android开发中非常容易被人忽略,因为这个概念在我们普通的开发中太少见了,很容易被忽略。

大家经过上面的学习应该知道,线程的消息都是放到同一个MessageQueue里面,取消息的时候是互斥取消息,而 且只能从头部取消息,而添加消息是按照消息的执行的先后顺序进行的排序,那么问题来了,同一个时间范围内的消 息,如果它是需要立刻执行的,那我们怎么办,按照常规的办法,我们需要等到队列轮询到我自己的时候才能执行 哦,那岂不是黄花菜都凉了。所以,我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。

同步屏障是什么?

屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?

//MessageQueue.postSyncBarrier()

    @UnsupportedAppUsage
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    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();
            //就是这里!!!初始化Message对象的时候,并没有给target赋值,因此 target==null 
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    //如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
                    prev = p;
                    p = p.next;
                }
            }
            //根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置 
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

可以看到Message对象初始化的时候并没有给 target 赋值,因此target == null 的来源就找到了。上面消息的插入也做了相应的注释。这样,一条 target == null 的消息就进入了消息队列。

那么开启同步屏障后,所谓的异步消息又是如何被处理的呢? 如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器 Looper.loop() 中,而 loop() 循环中会调用 MessageQueue.next() 从消息队列中进行取消息。

//MessageQueue.java
    
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。    
        // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。    
        // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)    
        // 如果期间有程序唤醒会立即返回
        int nextPollTimeoutMillis = 0;
        //next()也是一个无限循环 
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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;  //当前链表的头结点
                //关键!!!如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    // 如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间
                    // 场景如常用的postDelay
                    if (now < msg.when) {
                         //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                         //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回 
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取到消息
                        mBlocked = false;
                        //链表操作,获取msg并且删除该结点
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {
                    // 没有消息,nextPollTimeoutMills复位
                    nextPollTimeoutMillis = -1;
                }
                ... //省略    
        }
    }

从上面可以看出,当消息队列开启同步屏障的时候(即标识为msg.target == null),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了同步屏障的存在,msg_2 和 msg_M 这两个异步消息可以被优先处理,而后面的 msg_3 等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用removeSyncBarrier()

同步屏障使用场景

似乎在日常的应用开发中,很少会用到同步屏障。那么同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。

比如在View更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals(),如下:

//ViewRootImpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //开启同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //发送异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

postCallback()最终走到了ChoreographerpostCallbackDelayedInternal()

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);   //发送异步消息
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
}

这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。

最后,当要 移除同步屏障 的时候需要调用ViewRootImpl#unscheduleTraversals()

void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

同步屏障总结

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的setAsynchronous(true)时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在loop()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

6.Handler源码面试问题

  1. 一个线程有几个 Handler?

    一个线程可以有N个Handler

  2. 一个线程有几个 Looper?如何保证?

    一个线程只有一个Looper,由ThreadLocal决定了。

  3. Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?

    在handler的handlerMessage的方法中执行外部方法,即执行Activity的方法,这时就持有里Activity对象。

    那在 recycleVIew的adpater的ViweHolder的静态内部类为什么没有内存泄漏问题呢? 生命周期的问题。

    因为在handler的messageQueue中的message的tag就是持有了handler,就持有了Activity。在MesageQueue的next一直在执行,生命周期比较长。

    enqueueMessage{ msg.target = this; } GC:JVM 可达性算法

  4. 为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?

    在ActivityThread中创建了Looper,在子线程中需要looper.prepare和looper.loop()

  5. 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

    Looper.quit()可以退出循环

    quit: 唤醒线程-> messagequeue ->null -> 退出loop

    无消息的时候进入阻塞状态

    在handler中两个方面的阻塞? 1) message 不到时间 ,自动唤醒 2) messageQueue为空,无限等待,添加一个消息可以唤醒

  6. 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?

    利用锁,synchronized:是一个内置锁,由JVM决定。

    synchronize(this):同一个messagequeue对象里面的所有的函数、代码块,都会受限。

    1个线程只有一个可以操作MessageQueue 的地方:因为一个线程只有一个MessageQueue。

    取消息、添加消息、退出都是利用加锁

  7. 我们使用 Message 时应该如何创建它?

    Message.obtain()

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

    线程没有处理任何操作,已经释放CPU资源,在底层采用wait的方式。有消息的是时候底层再唤醒。

  9. 为什么是在子线程发送消息,而在主线程收消息?

    子线程:发送消息的调用函数所在的线程 ,这个函数就在子线程里面

    thread: handler.sendMessage(msg) -> MessageQueue.enequeMessage(msg)

    主线程Loop(): 轮询 MessageQueue ->msg

    static final ThreadLocal sThreadLocal = new ThreadLocal();

    所有得线程 sThreadLocal 都是同一个。

7.HandlerThread是什么?为什么它会存在?

作为一个Android开发,Handler机制是一定要了解的。在我面试过程中,发现很多人对Handler和Looper机制非常了解,对答如流,但是却不知道何为HandlerThread。

HandlerThread是Thread的子类,严格意义上来说就是一个线程,只是它在自己的线程里面帮我们创建了Loope。

HandlerThread 存在的意义

方便使用

  • 方便初始化
  • 方便获取线程looper

保证了线程安全

我们一般在Thread里面线程Looper进行初始化的代码里面,必须要对Looper.prepare(),同时要调用Loop. loop();

@Override public void run() {    
    Looper.prepare();    
    Looper.loop(); 
}

而我们要使用子线程中的Looper的方式是怎样的呢?看下面的代码

Thread thread = new Thread(new Runnable() {    
    Looper looper;    
    @Override    
    public void run() {      
        //  Log.d(TAG, "click2: " + Thread.currentThread().getName());        
        Looper.prepare();        
        looper =Looper.myLooper();        
        Looper.loop();    
    }
    
    public Looper getLooper() {        
        return looper;    
    } 
}); 
thread.start(); 
Handler handler = new Handler(thread.getLooper());

上面这段代码有没有问题呢?

  1. 在初始化子线程的handler的时候,我们无法将子线程的looper传值给Handler,解决办法有如下办法:

    • a. 可以将Handler的初始化放到Thread里面进行
    • b. 可以创建一个独立的类继承Thread,然后通过类的对象获取。

    这两种办法都可以,但是这个工作HandlerThread帮我们完成了。

  2. 依据多线程的工作原理,我们在上面的代码中,调用 thread.getLooper()的时候,此时的looper可能还没有初 始化,此时是不是可能会挂掉呢?

上面的两个问题 HandlerThread 已经帮我们完美的解决了,这就是handlerThread存在的必要性了。

我们再看 HandlerThread源码:

public void run() {    
    mTid = Process.myTid();    
    Looper.prepare();     
    synchronized (this) {        
        mLooper = Looper.myLooper();        
        notifyAll();  //此时唤醒其他等待的锁,但是要等执行完之后才会执行其他线程的锁
    }    
    Process.setThreadPriority(mPriority);    
    onLooperPrepared();    
    Looper.loop();    
    mTid = -1; 
} 
 public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();  //此时会释放锁,等待looper被初始化
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

它的优点就在于它的多线程操作,可以帮我们保证使用Thread的handler时一定是安全的。

8.IntentService

Service一般用于处理后台耗时任务。

应用需求:一项任务分成多个子任务,子任务按照顺序先后执行,子任务全部执行完后,这项任务才算成果

这个需求可以用多个线程来处理,一个线程处理完->下一个线程->下一个线程

IntentService就可以帮我们完成这个工作。而且能够很好的管理线程,保证一个子线程处理工作,而且是一个一个的完成任务,有条不紊的进行。

处理完-> service 自动停止:内存释放

到底还有别的地方用么? 很多地方有这么用 fragment什么生命周期管理,参考:www.jianshu.com/p/317b2d6bd…