Handler源码分析(二)

179 阅读11分钟

1. Handler相关问题(二)

  • 针对上一篇Handler相关问题(一)之后,篇幅原因,还有一些其他关于Handler的问题
  1. Handler的同步屏障
  2. IdelHandler的原理以及使用
  3. HandlerThread原理
  4. Handler如何实现线程的切换

2. Handler同步屏障源码分析

  • Message分为同步消息异步消息以及屏障消息
  • 通常发送的普通消息也即同步消息, Handler相关问题(一)中有讲到,这篇就不再赘述了

2.1 异步消息

  • 当一个消息为异步消息时,它将不会像同步消息那样受到同步屏障的影响
  • 异步消息的创建方式主要有两种,分别是Handler构造message.setAsynchronous,本质上都是调用的message.setAsynchronous
// 设置一个消息是否为异步消息,异步消息不会受到同步屏障的影响
public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}
  • Handler构造器的最后一个参数async就与异步消息有关
public Handler(@Nullable Callback callback, boolean async) {
   ...
    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; // 主要就是将async赋值给mAsynchronous
}
  • 关注这个全局变量mAsynchronous真正起作用的是handler.enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        // 当mAsynchronous为true,即Handler构造器中async参数为true时,会将该消息设置为异步消息
        // 本质上还是调用的msg.setAsynchronous,只不过利用该Handler发送的消息默认就全都是异步消息了
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • 先下个结论:当没有开启同步屏障时,异步消息和同步消息是没有区别的;而一旦开启了同步屏障,同步消息会被挡住而异步消息则会正常执行,具体后面会给出答案

2.2 同步屏障

  • 一般来说,MessageQueue里面的所有Message是按时间先后顺序进行排列的
  • 插入屏障消息开启了同步屏障之后,顾名思义,它将会挡住后面的所有同步消息,而异步消息将不会受到影响,无形中提高了异步消息执行的优先级,只有撤销同步屏障才会继续执行同步消息
  • 同步屏障的开启方法
/**
 * 发布一个同步屏障,注意这个方法必须与移除同步屏障方法removeSyncBarrier成对调用
 * @hide  这个方法已经被设置成不可见了,通常只有系统的代码才可以调用,譬如系统更新UI
 * 在draw/requestLayout、invalidate等很多方法都内部调用了viewRootImpl.scheduleTraversals,这个方法
 * 内部就发送了一个同步屏障,当然它也是跟viewRootImpl.unscheduleTraversals成对调用的
 */
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

// 开启同步屏障的本质就是插入一个屏障消息,when默认为当前时间
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();
        // 构建一个屏障消息,屏障消息与普通消息的本质就是target(即handler)为null
        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;
    }
}
  • 移除同步屏障
/**
 * 遍历queue找到屏障消息,移除回收该消息
 * @hide
 */
@UnsupportedAppUsage
@TestApi
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;
        // 循环遍历queue中所有的message,找到屏障消息移除并回收
        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);
        }
    }
}
  • 可以看到开启屏障这个方法现在已经不对开发者开放了(@hide),只是更新系统源码里会使用到,譬如下面ViewRootImpl中的scheduleTraversals方法,该方法会被draw、requestLayout、invalidate等方法中调用
@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 发送屏障消息,开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}
  • 知道了怎么发送和移除屏障消息,接下来看看屏障消息是如何隔离同步消息的,针对上一篇文章就知道应该是在MessageQueue的next方法中处理的,直接上相关源码
Message next() {
    ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ...
        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.
                // 当前消息是一个屏障消息,就需要获取messageQueue中所有的异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;  // msg赋值为第一个异步消息
                    // 不断遍历,直至找到异步消息或遍历完所有的消息
                } 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不为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;
            }
        ...
        }
        ...
    }
}
  • 具体代码逻辑可见,当有屏障消息时,屏障消息后面的异步消息会得到执行,而同步消息会被一直过滤掉

2.3 总结

  • Handler机制中的消息中的消息分为普通消息、异步消息以及屏障消息
  • 同步消息就是我们普通方式发送的消息,消息默认发送的也是普通消息
  • 异步消息可以通过设置message.setAsynchronous(true),或者直接在Handler的构造器中将最后一个参数async设置为true即可,通过该handler发送的消息默认都是异步消息(因为本质上是在发送消息时默认会调用message.setAsynchronous(true))
  • 同步屏障是通过handler.postSyncBarrier()开启的,实质上就是向MessageQueue中插入一个屏蔽消息(标志是message.target=null);开启之后需要在合适的位置通过removeSyncBarrier关闭,即移除屏障消息。
  • 开启同步屏障插入屏障消息后,MessageQueue中处于该屏障消息之后的异步消息将会不受影响,而同步消息将会被阻隔,达到一种同步屏障的功效,这也是同步消息与异步消息的唯一区别
  • 开启与关闭同步屏障的方法已被设置成@hide仅供源码内部使用;源码中典型的应用就是ViewRootImpl中的scheduleTraversals方法,该方法会被draw、requestLayout、invalidate等方法中调用

3. IdelHandler

MessageQueue.java

/**
 * Callback interface for discovering when a thread is going to block waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}
  • IdelHandler是MessageQueue的一个静态内部接口,当所在线程的MessageQueue所有即时消息已处理完毕,后续消息还未到执行时间或无更多消息,当前处于一个空闲状态,他内部的queueIdle将会回调
  • queueIdle()需要一个boolean类型的返回值,当返回true时,将在后续所有的临时空闲状态均会回调,而返回false时,只会在处于第一次临时空闲时回调,回调一次后IdleHandler会被移除

3.1 源码分析

3.1.1 addIdleHandler、removeIdleHandler

  • addIdleHandler最终是将IdleHandler接口实现类加入到ArrayList类型的mIdleHandlers集合中
  • removeIdleHandler则是将IdleHandler接口实现类从mIdleHandlers集合中移除
// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

3.1.2 messageQueue.next

Message next() {
    ...
    int pendingIdleHandlerCount = -1; // 初次执行会置为-1
    
    for (;;) {
      ...
        synchronized (this) {
         ...
            // 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.
            // 首次执行并且messageQueue中下一条消息为null或者还未到下一条消息的执行时间
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                // 获取存储IdleHandler类型集合mIdleHandlers的大小赋值给pendingIdleHandlerCount
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            // 假如集合mIdleHandlers中数量为0则跳出继续下一次for循环
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            // 将集合转化为IdleHandler[]类型的临时mPendingIdleHandlers数组,以便回收
            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.
        // 遍历数组回调IdleHandler的queueIdle方法
        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 {
                // 获取queueIdle的返回值并赋值给keep
                keep = idler.queueIdle(); 
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            // keep为false即queueIdle的返回值为false,则在mIdleHandlers中移除
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        // 每次循环后将pendingIdleHandlerCount置为0,使得只会执行一次就跳入下一次for循环,
        // 而不至于多次执行
        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;
    }
}

3.2 IdelHandler的使用

  1. 应用启动时,希望一些不重要的操作延迟执行,虽然可以使用postDelayed实现,但具体的延迟时间并未可知,可使用IdelHandler在messageQueue空闲时执行
  2. 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行
  3. ActivityThread中的handleResumeActivity会在最后一行执行Looper.myQueue().addIdleHandler(new Idler());进行一些回收的操作
  4. ActivityThread中的scheduleGcIdler()方法调用Looper.myQueue().addIdleHandler(mGcIdler);在闲事进行GC操作
  5. LeakCanary进行内存泄漏检测也并不是onDestry方法执行完成后就进行垃圾回收和一些分析的,而是利用IdleHandler在空闲的时候进行这些操作的,尽量不去影响主线程的操作

4. HandlerThread原理

  • 官方为我们在子线程中使用Handler、Looper提供的一个封装好了的Thread(继承自Thread类),防止出错
  • 源码也就100多行,给开发者提供了一些相对应的API,下面展示下其中比较主要的方法

4.1 构造器

  • HandlerThread没有无参构造,至少得设置线程名
// HandlerThread的构造器
// name会赋值给父类Thread,表示线程名称
public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

// priority表示线程执行优先级,可使用Process类的一些常量
public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority;
}

4.2 run

  • run方法中会自行调用Looper.prepare()以及Looper.loop(),这样就省去开发者自行处理了
  • HandlerThread专门提供了一个可重写的onLooperPrepared()方法,用于开发者在Looper初始化之后,调用Looper.loop之前的一些操作
@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    // 会在这里设置线程优先级
    Process.setThreadPriority(mPriority);
    // 这是给开发者提供的一个可重写的方法,用于Looper初始化之后,调用Looper.loop之前的一些操作
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

// 源码是一个空实现,仅提供给开发者用于Looper初始化之后,Looper.loop之前的一些操作
protected void onLooperPrepared() {
}

4.3 其他一些方法

  • 还提供了包含获取HandlerThread子线程中的Looper实例的方法handlerThread.getLooper(),以及获取handler实例的方法handlerThread.getThreadHandler()
  • 当然还封装了子线程中使用Handler/Looper的关键点:在子线程中不需要处理更多消息,即子线程维护的MessageQueue无消息时需要调用的looper.quit()使子线程退出,HandlerThread也提供了两个可供退出的方法handlerThread.quit()以及handlerThread.quitSafely()本质上也是调用了looper.quit()以及looper.quitSafely()
// handlerThread.quit方法
// handlerThread.quitSafely只是将下面注释中的looper.quit()替换成了looper.quitSafely()
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        // 本质上调用了looper.quit
        looper.quit();
        return true;
    }
    return false;
}

4.4 总结

  • HandlerThread是一个继承自Thread的线程类
  • 在run方法中帮我们自动调用了Looper.prepare()以及Looper.loop(),也提供了一个可供重写的onLooperPrepared()方法,执行时间是Looper初始化之后,Looper.loop之前,可供开发者做一些需要的操作
  • 提供了获取HandlerThread子线程中的Looper、Handler的方法handlerThread.getLooper()handlerThread.getThreadHandler()
  • 提供了该子线程MessageQueue无消息时的退出方法handlerThread.quit()handlerThread.quitSafely()

5. Handler如何实现线程的切换

  • 子线程中通过handler.sendMessage或者post方式发送消息,本质上调用的是messageQueue.enqueueMessage
  • 发送消息后通过Looper.loop轮询消息本质上也是通过messageQueue.next获取消息
  • 获取消息后会使用disptchMessage方法来回调handleMessage方法
  • 综上所述:handleMessage方法执行的线程是由messageQueue/looper所在线程来决定的,也就是说最终handleMessage回调执行的线程与handler.sendMessage/post执行的线程无关,而是与messageQueue/Looper所在线一致;当使用handler向其他线程的messageQueue发送消息即可使得最终执行的回调方法handleMessage执行在其他线程,也就完成了线程之间的切换工作