Handler机制详解

作者: Jack_Ou | 来源:发表于2021-01-27 19:21 被阅读0次

1.Handler的诞生

由于Android采用的是单线程模式,开发者无法在子线程中更新 UI,因此在Android开发中,经常会在子线程中进行一些操作,当操作完成之后,将结果发送到主线程进行显示。探索其背后的模式:子线程、Handler和主线程三者组成了生产者和消费者模式。子线程负责生产数据,主线程负责消费数据,而Handler负责将数据从子线程抛到主线程中。详细见下图

Handler生产者消费者模型.png

2.Handler相关的类

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

2.1 Looper的创建

创建Looper的方法是调用Looper.prepare() 方法或者Looper.prepareMainLooper()

// threadlocal保证一个线程只有一个looper
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { //如果prepare()被调用两次就会抛异常
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));//但Thread的map中没有looper,就创建一个
}

@Deprecated //该方法被废弃的原因是:Android帮我们调用了,自己不能调用
public static void prepareMainLooper() {
    prepare(false); // 调到上述方法,创建looper
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

2.2 创建MessageQueue以及与Looper和当前线程绑定

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);// 创建MessageQueue
    mThread = Thread.currentThread();//与当前线程绑定
}

// quitAllowed参数子线程传入的true,主线程传入false,代表主线程不允许销毁队列
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

2.3 Looper.loop()方法

public static void loop() {
    final Looper me = myLooper();   //从threadlocal中拿到调prepare()创建的looper
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    ......

    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;

    ......

    for (;;) {
        //不断从消息队列取消息。
        //queue.next()取消息的过程利用了epoll机制,当没有消息的时候阻塞,当有消息时,向管道写入一个字节数据,唤醒线程取消息。
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;//如果队列返回null,说明quit()被调用,需要退出线程
        }

        ......
        try {
            // 回调抛message的Handler的dispatchMessage,最终会调到handler接收到的callback.handleMessage(msg)中或者handler重写的handleMessage(msg)中。
            msg.target.dispatchMessage(msg);
        ......
        //回收处理完的消息(Message采用了享元模式,防止不停的new Message导致内存抖动)
        msg.recycleUnchecked();
    }
}

2.4 Handler的创建

//方式1
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //处理自己抛的消息
    }
};
//方式2 传入main looper或者子线程looper,通常传MainLooper
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}
//方式3 传入looper和callback,消息处理在callback中
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

2.5 Message的创建

可以直接new Message()但是不建议这么使用,因为如果有大量的new Message()然后用完了就被回收,这样会导致内存抖动。推荐的方式是使用obtain()系列方法。Message使用了享元模式,内部维护了一个对象池(最大50个),管理者对象的创建和销毁。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next; //从对象池头部取一个message
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
//Message中有一系列obtain()方法,主要是完成handler绑定或者callback绑定工作,就不全列举了
public static Message obtain(Handler h) //Message和handler绑定
public static Message obtain(Handler h, Runnable callback)//Message与handler和Runnable绑定
public static Message obtain(Handler h, int what) //Message与handler和事件码绑定

2.6 Message和Handhler绑定

  • 方式1:通过上面创建Message时绑定

  • 方式2:在发送消息的到时候绑定(代码如下)

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this; //绑定handler
    ......
    return queue.enqueueMessage(msg, uptimeMillis);
}

2.7 Handler发送消息

如下图调用关系,Handler发送消息的重载方法很多,但是主要只有2种。 sendMessage(Message) sendMessage方法通过一系列重载方法的调用,sendMessage调用sendMessageDelayed,继续调用sendMessageAtTime,继续调用sendMessageAtTime,继续调用enqueueMessage,继续调用MessageQueue的enqueueMessage方法,将消息保存在消息队列中。

Handler发送消息流程.png

2.8 Handler消费消息

当Looper取出,交给Handler的dispatchMessage进行处理

我们可以看到在dispatchMessage方法中,message中callback是一个Runnable对象,如果callback不为空,则直接调用callback的run方法,否则判断mCallback是否为空,mCallback在Handler构造方法中初始化,在主线程通直接通过无参的构造方法new出来的为null,所以会直接执行后面的handleMessage()方法。

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        //callback在message的构造方法中初始化或者使用handler.post(Runnable)时候才不为空
        handleCallback(msg); 
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {//如果callback不为空就调用callback的run()方法
                return;
            }
        }
        //如果没有Runnable,就回调handler重写的handleMessage()方法。
        //注意,不管是否设置callback都会回调handleMessage()方法
        handleMessage(msg);
    }
}

3.难点问题

3.1 消息加入和取出如何保证线程安全

MessageQueue在消息入队的时候和取消息的时候都会对队列加锁,保证要么入队消息,要么出队消息。

// 消息入队
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //当入队的时候对消息队列加锁
    synchronized (this) {
        ......
}
// 消息出队
Message next() {
        .....
        for (;;) {
            //循环等待消息
            ......
            //消息出队过程加锁
            synchronized (this) {
                ......
}

3.2 消息机制之同步屏障

通过上面分析,通常情况下Handler抛的消息会按照时间排序,然后Looper从头部开始一个一个取消息执行。但是有个需求是在一个小的时间范围内,handler抛的消息需要优先执行,那我们应该如何处理呢?例如UI需要立马重绘,而且需要优先处理这个消息,那就要用到消息同步屏障。

MessageQueue.postSyncBarrier() //开启同步屏障

MessageQueue.removeSyncBarrier() //移除同步屏障

Message.setAsynchronous(true) //设置为异步消息

3.2.1 开启同步屏障
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}
//注意,方法调用返回一个屏障的token,到时候删除的时候会用到这个token
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token
    synchronized (this) {
        final int token = mNextBarrierToken++;
        //从消息池中获取Message
        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==null。

3.2.2 处理异步消息

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

//MessageQueue.java

Message next() {
    .....//省略一些代码
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            //获取系统开机到现在的时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages; //当前链表的头结点
            
            //关键!!!
            //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
            if (msg != null && msg.target == null) {
            // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next; //=====重新给msg赋值,达到优先返回异步消息的作用===
                } while (msg != null && !msg.isAsynchronous());//如果有异步消息
            }
            if (msg != null) {
                //如果有时间到的消息就返回,否则就等待。
            }
            .....//省略

}

如果开启了同步屏障,在返回的msg中会被重新赋值,所以如果开启了同步屏障,MessageQueue会遍历队列中是否有时间到了的异步消息,如果有就重新给msg赋值让他返回出去给handler处理。

示意图如下:

同步屏障示意图.png
3.2.3 同步屏障的使用场景

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

//ViewRootImpl.java
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //发送异步消息
        
        // Choreographer.postCallback最终会调用到postCallbackDelayedInternal(),在这里面设置为异步消息并且发送到消息队列中
        // mTraversalRunnable中会移除同步屏障并且执行UI更新
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
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;
                //将消息设置成异步消息并且发送到消息队列中,消息队列会根据target==null时取出异步消息执行
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

3.3 同步执行消息

3.3.1 runWithScissors()分析

我们从前面的分析可以看出生产者-消费者模式其实是异步的执行操作。生产者生产好了商品向容器里面放,消费者只管从容器取就行,但是有这么一个场景,就是我生产的东西需要消费了才能继续生产下去,那么就需要考虑同步执行消息了。(可能举例不恰当)但是在framework中,WindowManagerService初始化的时候,就会出现这样的情况,WindowManagerService的初始化是放在显示子线程完成的,但是使用多线程来初始化服务虽然加快了初始化速度,但是也会出现一种情况就是某个服务没有初始化结束,导致其他服务运行异常的情况。因此系统提供了Handler.runWithScissors()这个方法来实现同步执行消息。如果任务没有执行结束,该线程不能执行其他的任务。下面我们来分析一下这个方法:

public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    ......
    // 创建一个带阻塞功能的Runnable,主要是传入待执行的任务
    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout); // 发送任务到消息队列,并且阻塞线程
}
public boolean postAndWait(Handler handler, long timeout) {
    if (!handler.post(this)) { //发送任务到消息队列
        return false;
    }

    synchronized (this) {
        if (timeout > 0) {
            final long expirationTime = SystemClock.uptimeMillis() + timeout;
            while (!mDone) {
                long delay = expirationTime - SystemClock.uptimeMillis();
                if (delay <= 0) {
                    return false; // timeout
                }
                try {
                    wait(delay); //有超时时间的阻塞线程
                } catch (InterruptedException ex) {
                }
            }
        } else {
            while (!mDone) {
                try {
                    wait();//阻塞线程
                } catch (InterruptedException ex) {
                }
            }
        }
    }
    return true;
}
// BlockingRunnable的run方法
public void run() {
    try {
        mTask.run();  //调用传入任务的run方法执行任务
    } finally {
        synchronized (this) {
            mDone = true;
            notifyAll(); //通知其他线程不再休眠
        }
    }
}
3.3.2 runWithScissors()使用场景
public static WindowManagerService main(final Context context, final InputManagerService im,final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
        ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,Supplier<Surface> surfaceFactory,
        Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
    //使用显示子线程来初始化WindowManagerService
    DisplayThread.getHandler().runWithScissors(() ->
            sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
    return sInstance;
}

参考文章:

同步屏障

相关文章

网友评论

    本文标题:Handler机制详解

    本文链接:https://www.haomeiwen.com/subject/twoazktx.html