android handler 消息机制知识点

219 阅读11分钟

Handler的使用

public Handler(Looper looper, Callback callback, boolean async)

val handler = object : Handler(Looper.getMainLooper(), object : Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true // 优先于 handleMessage,返回true的话 handleMessage 就不再执行
        }
    }) {
        override fun handleMessage(msg: Message) {
        }
    }
handler.sendMessage()
handler.sendMessageAtTime()
handler.sendEmptyMessageDelayed()
handler.sendMessageAtFrontOfQueue()
handler.post()
  1. boolean async: Handler发送的消息是否要设置成异步消息,异步消息用于同步屏障。
  2. Callback: 如果设置了该参数,Handler在dispatchMessage时会优先调用callback;返回为true,则不再执行handleMessage
  3. Looper: 每个Handler都有一个对应的Looper,如果没有显式设置Looper,那么Handler会取该线程对应的Looper赋给该Handler。子线程中必须手动创建Looper(prepare)。

消息机制工作流程

  • Handler:通过send message或post runnable到Looper处理,post最终也是调用send方法调用。
  • MessageQueue: 它内部使用一个 Message 链表实现消息的存和取。 链表的排列依据是 Message.when,表示 Message 期望被分发的时间,该值是 SystemClock. uptimeMillis() 与 delayMillis 之和。主要是两个操作,插入和读取,读取伴随着删除。通过enqueueMessage完成入队的操作,即链表的插入,而next里面是一个死循环,当有消息时会通过调用next方法从单链表里移出,没有时则阻塞
  • Looper:Looper.prepare方法会为当前线程创建一个Looper,一个线程只能对应一个 Looper,再创建时会报错;而Looper.loop这是开启消息循环;Looper.myLooper() 获取当前线程的Looper,其实调用sThreadLocal.get()。
  • loop是一个死循环,必须执行loop才能开始处理msg. 当MessageQueue的next返回null时则退出循环,否则就与next一起阻塞,否则就通过target调用handler的dispatchMessage处理消息。

发送 message 时,通过 enqueueMessage 加入消息队列队列 MessageQueue。而Loop也是一个循环,不断调用 MessageQueue 的 next 方法来读取消息,next阻塞Loop也阻塞。

//handler与 MessageQueue、Looper的关联
public Handler(@Nullable Callback callback, boolean async) {
    mLooper = Looper.myLooper(); //sThreadLocal.get(),这里是直接取。所以子线程一定要先创建好
    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;
}

//Looper的创建
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));
}

//MQ的创建
private Looper(boolean quitAllowed) {//quitAllowed 是否允许退出Looper
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

message

  • message 支持复用,未使用的 message 以单链表形式维持关系,sPool指向的对象永远是单链表的第一个元素。
  • MessageQueue 中 message 排列依据是 Message.when,表示 Message 期望被分发的时间,该值是 SystemClock.uptimeMillis() 与 delayMillis 之和。uptimeMillis: Returns milliseconds since boot, not counting time spent in deep sleep。
  • target 指向发送此消息的 handler,后续 Looper 派发消息时要知道是哪个 handler。
public final class Message implements Parcelable {
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;
    /*package*/ Bundle data;
    /*package*/ Handler target;
    /*package*/ Runnable callback;
    /*package*/ Message next;

    
    //Message 以单链表形式维持关系,sPool指向的对象永远是单链表的第一个元素
    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();
    }
    
    // 消息回收
    void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        when = 0;
        target = null; //...
    
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
	}
}

MessageQueue

  • enqueueMessage、next 取消息时需要加锁保证多线程下链接操作的安全,因为发送消息时可能会从多个线程上。
  • 当 无消息或者消息的when不到时间时, 会调用 nativePollOnce 使线程阻塞,让出cpu调度;通过 nativeWake 唤醒线程
  • 如果使用wait和notify来阻塞或唤醒线程,只能处理java层的消息,对于系统的消息不能处理。
public final class MessageQueue {
    Message mMessages; // 指向队首
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); // IdleHandler
    private IdleHandler[] mPendingIdleHandlers;
    private native void nativePollOnce(long ptr, int timeoutMillis); // 队列休眠,looper 也会阻塞
    private native static void nativeWake(long ptr); // 唤醒队列

	boolean enqueueMessage(Message msg, long when) {
        // 加锁,保证线程安全
        synchronized (this) {
            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 {
               // 按 when 顺序插入链表中
                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;
            }

            //是否需要尝试唤醒队列
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

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


    // 同步屏障相关
    public int postSyncBarrier() { }
    private int postSyncBarrier(long when) { }
    public void removeSyncBarrier(int token) { }
    
    // IdleHandler 相关
    public void addIdleHandler(IdleHandler handler) {  }
    public void removeIdleHandler(@NonNull android.os.MessageQueue.IdleHandler handler) {}

    // 其它 message 操作相关
    boolean hasMessages(Handler h, Runnable r, Object object) { }
    boolean hasMessages(Handler h) { }
    void removeMessages(Handler h, int what, Object object) { }
    void removeEqualMessages(Handler h, int what, Object object) { }
    ......
}

Looper


public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    final MessageQueue mQueue;

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

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        for (; ; ) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

    private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();

        return true;
    }

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

    public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
}
  • 如果是想创建子线程的Handler,子线程默认没有Looper的,需要如下:
Looper.prepare();// 创建Looper
Handler handler = new Handler();
Looper.loop();
  • 如果是创建主线程的Handler,则需要传入MainLooper: Handler mainHandler = new Handler(Looper.getMainLooper());

异步消息与同步屏障

  1. 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
  2. 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
  3. postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  4. postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
  5. 插入普通消息会唤醒消息队列,但是插入屏障不会。
  6. 移除同步屏障的方法是MessageQuene.removeSyncBarrier(int token),token就是添加同步屏障时候返回的唯一标示.

通过构造参async为true的Handler发送的Message都为异步消息,或者 调用msg.setAsynchronous(true); 异步消息一般与同步屏障配合使用。

//构造一个发送处理异步消息的Handler
Handler mHandler = new Handle(true)
//通过这个mHandler发送的Message,在queueMessage时候,都会把Message的asynchronous设置为true,即异步消息
    
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //将Handler赋值给Message的target变量
    msg.target = this;
    //mAsynchronous为true,为异步消息
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。

//mHandler.getLooper().getQueue().postSyncBarrier();
private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        //1、屏障消息和普通消息的区别是屏障消息没有tartget。
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        
        Message prev = null;
        Message p = mMessages;
        //2、根据时间顺序将屏障插入到消息链表中适当的位置
        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;
        }
        //3、返回一个序号,通过这个序号可以撤销屏障
        return token;
    }
}

那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。

Message next() {
    //1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
    nativePollOnce(ptr, nextPollTimeoutMillis);
    synchronized (this) {
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {//2、遇到屏障  msg.target == null
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
        }
        if (msg != null) {
            //4、如果找到异步消息
            if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间)
                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;
                msg.markInUse();
                return msg;
            }
        } else {
            // 如果没有异步消息就一直休眠,等待被唤醒。
            nextPollTimeoutMillis = -1;//nativePollOnce 传入-1即可
        }
    }
}

IdleHandler

  • IdleHandler 就是 MessageQueue为空或者目前没有需要执行的Message时会回调的接口对象
  • 当前队列是否空闲:mMessages == null || now < mMessages.when
  • IdleHandler queueIdle() 返回true就是单次回调后不删除,下次进入空闲时继续回调该方法,false只回调单次。
//当前队列将进入阻塞等待消息时调用该接口回调,即队列空闲
Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
    override fun queueIdle(): Boolean {
        //返回true就是单次回调后不删除,下次进入空闲时继续回调该方法,false只回调单次。
        return false
    }
})
    
    
public final class MessageQueue {
   //判断当前队列是不是空闲的,辅助方法
    public boolean isIdle() {
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            return mMessages == null || now < mMessages.when;
        }
    }
    
    ......
    //Looper的prepare()方法会通过ThreadLocal准备当前线程的MessageQueue实例,
    //然后在loop()方法中死循环调用当前队列的next()方法获取Message。
    Message next() {
        ......
        for (;;) {
            ......
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ...

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //循环遍历所有IdleHandler
            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 {
                    //调用IdleHandler接口的queueIdle方法并获取返回值。
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                //如果IdleHandler接口的queueIdle方法返回false说明只执行一次需要删除。
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ......
        }
    }
}

线程切换是如何做到的

在子线程中sendMessage(Message)发送消息,然后在Handler的handleMessage(Message)接收消息,执行更新UI操作。那么Handler是如何把消息从MyThread传递到MainThread中来呢?
没有什么所谓的线程的切换。消息的回调线程由Looper所在线程决定。Looper 所在线程内部有一个 while(true),有消息就调用回掉函数,没有就 wait。Handler 只是一个用于收发消息的作用。因为 Handler 的 dispatchMessage 方法是在创建 Handler 的线程中的 Looper 内部调用的,因此 Looper 所处的线程也就决定了你 Handler 提交任务执行所在的线程。handler 所属线程的 Looper 会将 msg 从 msgqueue 中取出然后执行相关逻辑。handler的执行跟创建handler的线程无关,跟创建looper的线程相关。

Thread thread=new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler=new Handler(getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this,"hello,world",Toast.LENGTH_LONG).show();
            }
        });
        Looper.loop();
    }
});
thread.start();

Looper 死循环与卡顿

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

HandlerThread

HandlerThread 继承 Thread,内部创建了Looper并循环。作用就是不断的从消息队列中取出Message,然后在当前线程执行。

在什么时候用HandlerThread? 可以使用HandlerThread来模拟『单线程+队列』结构,HandlerThread就相当于是一个SingleThreadExecutor结构,即单线程池,在App启动的时候,如果有很多初始化工作不需要在UI线程完成,那么可以将这些任务发送至HandlerThread,然后顺序完成,加快应用的启动速度

HandlerThread extends Thread {
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}


// 使用步骤如下
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()
Handler workHandler = new Handler(handlerThread.getLooper() ) {
        @Override
        public boolean handleMessage(Message msg) {
            ...//消息处理
            return true;
        }
    });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
Message msg = Message.obtain();
workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();

如下使用,作用类似于 Executors.newSingleThreadExecutor():

private val lightWorkThread = HandlerThread("LightThread", THREAD_PRIORITY_BACKGROUND).apply {
    start()
}

private val lightWorkHandler: Handler = Handler(lightWorkThread.looper)
private val lightWorkExecutor: Executor = HandlerExecutor(lightWorkHandler)
private val lightWorkDispatcher: CoroutineDispatcher = lightWorkExecutor.asCoroutineDispatcher()
private var lightWorkScope: CoroutineScope = CoroutineScope(lightWorkDispatcher)

class HandlerExecutor(private val handler: Handler) : Executor {
    override fun execute(command: Runnable?) {
        command?.let { handler.post(it) }
    }
}