Android FrameWork 之Handler攻略

1.1 Handler概述

在Android开发中,经常会在子线程中进行一些操作,当操作完毕后会通过handler发送一些数据给主线程,通知主线程做相应的操作。

探索其背后的原理:子线程、handler 、主线程,其实构成了线程模型中的经典问题 生产者-消费者 模型。

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

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

Handler:发送和接收消息。

Looper:用于轮询消息队列,一个线程只能有一个Looper。

Message:消息实体。

MessageQueue:消息队列用于存储消息和管理消息。

1.2 同步屏障

Handler的消息,可以分为三种:

  • 同步消息:异步flag为false,一般构造的msg默认为同步消息。
  • 同步屏障消息:MessageQueue.postSyncBarrier()会构建一个msg.target==null的同步屏障消息,进而开启同步屏障。
  • 异步消息:异步flag为true,若是开启了同步屏障,MessageQueue.next()中会遍历查找异步消息进而优先执行。

2.1 Handler全解

image.png

图解:

  • Handler通过sendMessage()发送Message到MessageQueue队列;
  • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
  • 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
  • 将Message加入MessageQueue时,队列如果处于阻塞状态则会唤醒loop线程;如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性的工作。

消息分发的优先级:

  1. Message的回调方法:message.callback.run(),优先级最高;
  2. Handler的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1;
  3. Handler的默认方法:Handler.handleMessage(msg),优先级最低。

消息缓存:

为了提供效率,Message提供了一个大小为50的对象池,减少对象不断创建与销毁的过程。

2.2 同步屏障

Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。比如,在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals(),如下:

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

Choreographer#postCallback()-->postCallbackDelayedInternal,如下:

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()中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

3.1 Handler

3.1.1 new Handler()

public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    // 匿名类、内部类或本地类都必须声明为static,否则会警告可能出现内存泄漏
    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());
        }
    }
    // 持有Looper的引用
    // 是从所在线程的ThreadLocal中获取,不是创建Looper实例
    mLooper = Looper.myLooper();
    
    // 子线程不调用Looper.prepare(),直接实例化Handler抛出该异常的原因
    // 主线程不需要,是因为应用启动的时候系统已帮我们启动了主线程的Looper
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    // 持有MessageQueue的引用
    mQueue = mLooper.mQueue;
    // 回调方法
    mCallback = callback;
    // 是否将要发送的msg设置为异步消息的flag
    mAsynchronous = async;
}

对于Handler的无参构造方法,默认采用当前线程TLS(ThreadLocal.ThreadLocalMap)中的Looper对象,并且callback回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的Looper对象。

public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Handler类在构造方法中,可指定Looper,Callback回调方法以及消息的处理方式(同步或异步),对于无参的handler,默认是当前线程的Looper。

3.1.2 sendMessage()

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    // 必须要有MessageQueue即Looper
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    // 将Handler与msg绑定
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    // 如果Handler的异步消息flag位true,则将该消息设置为异步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 最后将消息交给MessageQueue处理
    return queue.enqueueMessage(msg, uptimeMillis);
}

Handler.sendEmptyMessage()等系列方法最终调用MessageQueue.enqueueMessage(msg, uptimeMillis),将消息添加到消息队列中,其中uptimeMillis为系统当前的运行时间,不包括休眠时间。

3.1.3 post()

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

3.1.4 dispatchMessage

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        // 当msg有回调方法时,回调msg.callback.run
        handleCallback(msg);
    } else {
        // 当Handler存在Callback成员变量时,回调方法mCallback.handlerMessage()
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 回调自身的回调方法handleMessage()
        handleMessage(msg);
    }
}

分发消息流程:

  1. Message的回调方法不为空时,则回调方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
  2. HandlermCallback成员变量不为空时,则回调方法mCallback.handleMessage(msg),否则进入步骤3;
  3. 调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。

对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑。

3.1.5 obtainMessage()

public final Message obtainMessage()
{
    return Message.obtain(this);
}

Handler.obtainMessage()方法,最终调用Message.obtainMessage(this),其中this为当前的Handler对象。

3.1.6 removeMessage()

public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

Handler是消息机制中非常重要的辅助类,更多的实现都是MessageQueue, Message中的方法,Handler的目的是为了更加方便的使用消息机制。

3.2 Looper

3.2.1 new Looper()

/**
 * 
 * @param quitAllowed looper是否允许退出
 */
private Looper(boolean quitAllowed) {
    // 构造函数中实例化MessageQueue与之对应
    mQueue = new MessageQueue(quitAllowed);
    // 记录当前线程
    mThread = Thread.currentThread();
}

3.2.2 prepare()

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

private static void prepare(boolean quitAllowed) {
    // 一条线程只能拥有一个looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 将Looper存入当前线程的threadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}

public static void prepareMainLooper() {
    // 设置不允许退出的Looper 
    prepare(false); 
    synchronized (Looper.class) { 
        // 将当前的Looper保存为主Looper,每个线程只允许执行一次。 
        if (sMainLooper != null) { 
            throw new IllegalStateException("The main Looper has already been prepared.");
        } 
        sMainLooper = myLooper(); 
    } 
}

3.2.3 loop()

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    // 获取TLS存储的Looper对象
    final Looper me = myLooper();
    // 子线程需要构造一个looper
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    ...
    
    // 死循环尝试从MessageQueue中获取msg
    for (;;) {
        // 可能会阻塞
        Message msg = queue.next(); 
        if (msg == null) {
            // 没有消息,则退出循环
            return;
        }
        
        ...
        
        try {
            // 回调消息所绑定的Handler的dispatchMessage()
            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);
            }
        }
        
        ...

        // 回收消息实例
        msg.recycleUnchecked();
    }
}

loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环。

  • 读取MessageQueue的下一条Message;
  • 把Message分发给相应的target;
  • 再把分发后的Message回收到消息池,以便重复利用。

3.2.4 quit()

public void quit() { 
    // 消息移除 
    mQueue.quit(false); 
} 

public void quitSafely() {
    // 安全地消息移除
    mQueue.quit(true); 
}

Looper.quit()方法的实现最终调用的是MessageQueue.quit()方法。

3.2.5 myLooper()

public static @Nullable Looper myLooper() { 
    // 获取TLS存储的Looper对象
    return sThreadLocal.get();
}

3.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);

3.3.1 new MessageQueue()

MessageQueue(boolean quitAllowed) {
    // 是否允许退出消息队列的flag
    // Looper实例化时会将flage传入
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

3.3.2 enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    // msg必须要绑定一个Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    // 采用锁同步机制,为了保证消息队列一次只会处理一个msg
    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        
        // 消息机制已退出,则释放该实例并return
        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;
        
        // 排序
        
        // 当前没有消息 或者 when==0 或者 when小于当前message的when时
        // 置于链表头结点
        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 {
            // 插入队列中间。通常我们不必唤醒事件队列,除非队列头部有屏障,并且消息是队列中最早的异步消息。
            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) {
            // 唤醒 nativePollOnece 的沉睡
            // 涉及底层的epoll机制
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

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

        // 采用锁同步机制,为了保证消息队列一次只会处理一个msg
        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) {
                // 则往后遍历查找异步消息进而执行
                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 {
                    // 获取一条消息,并返回
                    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 {
                // 没有消息
                nextPollTimeoutMillis = -1;
            }

            // 处理完所有待处理的消息后处理退出消息
            if (mQuitting) {
                dispose();
                return null;
            }
            
            
            // IdleHandler,启动优化的方案之一
            // 如果第一次空闲,则获取要运行的空闲器的数量。空闲句柄仅在队列为空或队列中的第一条消息(可能是屏障)将在未来处理时运行。
            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);
        }

        // 运行空闲处理程序。我们只在第一次迭代期间到达这个代码块。
        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);
                }
            }
        }

        // 将空闲处理程序计数重置为 0,这样我们就不会再次运行它们。
        pendingIdleHandlerCount = 0;

        // 在调用空闲处理程序时,可能已经传递了一条新消息,因此无需等待即可返回并再次查找未决消息。
        nextPollTimeoutMillis = 0;
    }
}

nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长;当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。

当处于空闲时,往往会执行IdleHandler中的方法。当nativePollOnce()返回后,next()从mMessages中提取一个消息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容