• Android消息机制(Handler)详述


    概述

    Android中的多进程、多线程中提过,只有主线程(UI线程)可以更新UI,其他线程不可以,所以一般耗时操作放到子线程。子线程可以通过Handler将相关信息通知到主线程。
    Android的消息机制主要是Handler机制。Handler的工作过程,还有两个重要部分MessageQueue(消息队列,下面简称MQ)和Looper。

    由于下面总结中穿插了不少源码 便于理解,导致篇幅比较长(加代码有600多行)。所以先大致总结并给出大致目录,提前了解是否是需要的内容。

    大致总结

    消息机制的大致流程

    1. 线程创建Looper(调用Looper.preper()),然后运行Looper.looper()开启循环。Looper是和线程绑定的,一个线程只能有一个Looper ,一个Looper内部维护一个MQ。
    2. Handler调用sendMessage()方法发送消息,将消息加入到MQ中(enqueueMessage())。
    3. Looper死循环,通过MessageQueue.next()取出符合的消息。
    4. 取出消息后,通过msg.target.dispatchMessage()分发消息。
    5. Handler对接受到的消息进行具体处理,调用dispatchMessage()。
    6. 调用Looper的quit()方法终止,即消息队列退出、looper循环退出。

    注意点

    1. 创建Handler的线程中一定先有Looper对象, 才能创建Handler,否则会抛异常。下面详解中通过代码就能看出来。
    2. 一个线程只能有一个Looper。但可以创建多个Handler
    3. MQ是Looper内存维护的。
    4. 主线程在应用启动后默认创建Looper 且不能退出。

    跨线程大致理解

    Handler能做到跨线程,主要是Looper及内部的消息队列。最常见的:程序启动主线程创建Looper并绑定了消息队列,在主线程创建Handler,这个Handler与Looper绑定的。在其他线程(任何地方)通过这个Handler发送消息,消息都加入到了主线程Looper内部的消息队列(消息发送到的MQ是 创建Handler时绑定的Looper内部MQ),当消息被Looper循环取出,自然就回到了主线程

    大致目录

    1       Looper、Handler与MQ     
    1.1     Looper     
    1.1.1   Looper的创建:prepare()     
    1.1.2   Looper循环:loop()    
    
    1.2     Handler     
    1.2.1   Handler的创建    
    1.2.2   Handler发送消息    
    1.2.3   Handler分派处理:dispatchMessage    
    
    1.3     MessageQueue    
    1.3.1   入队:enqueueMessage()    
    1.3.2   next()方法    
    1.3.3   退出:quit()    
    
    2       其他注意点      
    2.1     Handler一般使用         
    2.2     消息池          
    2.3     子线程到主线程的方法            
    2.4     主线程的Looper         
    2.5     ANR问题       
    

    Looper、Handler与MQ

    Looper

    Looper是循环器,为一个线程运行消息循环,不断检查消息队列中是否有新的消息。
    Looper.prepare()为当前线程创建一个looper,并在其内部维护一个MQ。
    Looper.loop()即looper开始工作,运行消息循环。

    下面是Looper部分的几处源码,有助于理解。

    Looper的创建:prepare()

    // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    @UnsupportedAppUsage
    final MessageQueue mQueue;
    final Thread mThread;
    
    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));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    Looper通过prepare()方法创建,主要有以下几点:

    1. 默认创建Looper时,设置参数的为true,即消息队列是能够退出的。MQ的退出也提供了安全退出和非安全退出两种方法(在下面MQ部分中详述)。
    2. 一个线程只能创建一个Looper,否则会抛出RuntimeException。
    3. Looper对象保存在ThreadLocal中的。ThreadLocal是一个线程内部的数据存储类,每个线程都有自己独立访问的变量副本。 libcore/ojluni/src/main/java/java/lang/ThreadLocal.java下有相关源码。
    4. Looper创建,即创建了一个内部消息队列mQueue,并绑定了当前线程。
    5. 主线程(UI线程)创建的Looper是不可退出的,应用启动后默认创建好了的。(下面有单独讲解)

    Looper循环:loop()

    Message.java
    @UnsupportedAppUsage
    /*package*/ Handler target;
    
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
    
    Looper.java
    public static void loop() {
        ......
        for (;;) {
            //不断取出下一条消息,mgs为null即消息队列退出,若没有消息且没有退出 消息队列一直阻塞的
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            try {
                //分派消息,通过Handler处理
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
            ......
            msg.recycleUnchecked();
        }
    }
    
    public static @Nullable
    Looper myLooper() {
        return sThreadLocal.get();
    }
    

    由于代码比较长,截取了关键代码,......表示该处有省略的代码。
    loop()是消息循环运行的关键,整体把握这里关注两行代码:Message msg = queue.next(); 和 msg.target.dispatchMessage(msg);。这两个分别在MQ部分和Handler部分有详述。

    1. for(;;)死循环,这是关键,真正的消息循环。跳出的唯一条件是queue.next()为null,但实际只有Looper执行quit()才能达成这一条件(next()方法MQ中详述)。
    2. next()不断取出下一条消息,mgs为null即消息队列退出,循环停止。若没有消息且没有退出 消息队列一直阻塞的
    3. 当next()返回一条消息,Looper调用msg.target.dispatchMessage(msg)进行分派处理。这里的msg.target就是一个Handler,即调用了Handler的dispatchMessage()方法(Handler中详述)。

    Handler

    Handler主要包含消息的发送和接收处理。

    Handler的创建

    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;
    
    /**
     * @hide
     */
    public Handler(@Nullable Callback callback, boolean async) {
        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());
            }
        }
    
        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;
    }
    

    注意几点:

    1. Handler有好几种构造方法,一种是指定了Looper,另一种没指定。所有构造方法都基本确定4个值:Hanlder关联的Looper;关联的MQ;关联的Callback;是否异步。
    2. 上述是没指定Looper最终的方法。从中可以看到,没有Looper就无法创建Handler。

    Handler发送消息

    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;
    }
    
    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;
        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) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    1. Handler通过post()、postDelayed()、sendMessage()、sendMessageDelayed()等方法发送消息,最终都是走到了上面的sendMessageAtTime()中。
    2. Handler各种发送方式,最终到sendMessageAtTime()。这个过程,参数uptimeMillis 即 消息发处的绝对时间也就是when。
    3. post()等发送方法传入的参数Runnable即消息的callback(msg.callback),在dispatchMessage中可以看到。
    4. 发送消息,Handler仅是将消息加入消息队列中(enqueueMessage()在MQ中详述),这个消息队列就是Handler在创建时绑定的Looper的内部消息队列。

    这里都是使用SystemClock.uptimeMillis(),简单说明下SystemClock.uptimeMillis()与System.currentTimeMillis()区别:
    System.currentTimeMillis()是1970年1月1日(UTC)到现在的毫秒值。
    SystemClock.uptimeMillis()是设备启动到现在的时间的毫秒值。(不包括深度睡眠)
    SystemClock.elapsedRealtime()是设备启动到现在时间的毫秒值。(包括深度睡眠)
    为什么基本都用SystemClock.uptimeMillis()作为时间间隔的获取方法呢?
    System.currentTimeMillis()通过设置设备的时间是可以改变的,这样设置后 那些计划的执行明显会发生异常。

    Handler分派处理:dispatchMessage

    @UnsupportedAppUsage
    /*package*/ Handler target;
    
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
    
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }
    
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(@NonNull Message msg) {
    }
    

    Looper循环通过queue.next()获取到一条消息,再通过Handler的dispatchMessage()分派处理。

    1. 若msg.callback不为空,即上述所说(Handler发送消息 部分)的是否传入了Runnable参数,有则执行Runnable。(Handler发送消息时传入)
    2. 如果msg.callback为空,在Handler创建时指定了Callback参数,即实现了handleMessage()的类作为参数,则直接执行handleMessage()。(Handler创建时传入)
    3. 若上面两种回调都不存在,可由handleMessage()处理。(创建的Handler重写该方法)

    MessageQueue

    消息队列MQ。主要列出Looper和Handler中提到的几个关于MQ的重要过程。
    消息队列是单链表实现的,这属于数据结构,了解的话可以参考数据结构之队列(Queue)

    入队:enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        //Handler为空
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //消息标记是使用中
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        
        //线程安全,同步
        synchronized (this) {
            //消息队列已退出,被放弃
            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;
            //队列是空或者msg比队列中其他消息要先执行,该msg作为队首入队。
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //加入队首。p是指向之前队首的,了解队列链表实现很容易理解
                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循环,跳出时:p指向null,prev指向队尾最后一个消息,即msg最后执行。
                //或者p指向第一个when大于msg的消息,prev则指向前面一个(最后一个when小于msg的消息)
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //msg插入到对应的位置
                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;
    }
    

    Handler发送消息,将消息加入了消息队列,即上面的enqueueMessage的方法。
    这个方法不难理解,可以看添加的中文注释。
    这里主要注意的是消息的处理时间,看入队逻辑 可以看出消息队列是按消息处理时间排队的。

    next()方法

    @UnsupportedAppUsage
    Message next() {
        ......
        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.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //还没有到消息处理时间,设置阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)
                    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 {
                    //没有消息要处理,nextPollTimeoutMillis设置为-1。
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
    
                // Process the quit message now that all pending messages have been handled.
                //消息队列退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ......
            }
            ......
        }
    }
    

    这个方法比较复杂,代码比较长。上面只截取了部分关键代码,可以看下添加的中文注释,能够理解。
    注意两个地方:

    1. 消息屏障(同步屏障)。可以通过MessageQueue.postSyncBarrier()设置,即msg.target为null。这里相当于异步优先。
    2. 仅当mQuitting为true时,即消息队列退出(quit()),next()才返回null。Looper的looper()循环才停止。

    最后来看下消息队列的退出

    退出:quit()

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
    
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
    
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
    
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
    
    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
    
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
    

    这个也不复杂,简单关注两点:

    1. quit()有安全退出和非安全退出,两者的差别从上面也能看到。非安全退出removeAllMessagesLocked(),直接退出 清空队列消息;安全退出removeAllFutureMessagesLocked(),消息处理时间在现在now之后的消息会被直接清空,而在now之前的会继续保留 由next()继续获取处理。
    2. quit()调用后才有mQuitting = true,这样next()才会返回null,最终Looper.looper()循环才会停止。

    其他注意点

    Handler一般使用

    Handler使用,一般是子线程进入主线程更新UI。下面是常见的操作。
    主要注意Hanler的创建(多种方式的选择)以及回调的处理,发送消息的方式。

    private final String TAG = "HandlerActivity";
    
    private final int MAIN_HANDLER_1 = 1;
    
    private Handler mMainHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage( msg );
            switch (msg.what) {
                case  MAIN_HANDLER_1:
                    //Do something. like Update UI
                    break;
            }
        }
    };;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        Log.d( TAG, "onCreate: MainHandler Looper=" + mMainHandler.getLooper() );
        SubThread subThread = new SubThread();
        subThread.start();
    }
    
    private class SubThread extends  Thread {
        @Override
        public void run() {
            //Do something
            Message message = mMainHandler.obtainMessage();
            message.what = MAIN_HANDLER_1;
            mMainHandler.sendMessage(message);
        }
    }
    

    消息池

    Message内部保存了一个缓存的消息池,我们可以通过Message.obtain()或者mMainHandler.obtainMessage()从缓存池获得一个消息对象。避免每次创建Message带来的资源占用。
    Message.obtain()的多种方法以及mMainHandler.obtainMessage()最终都是调用obtain()从消息池中获取一个消息对象。

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

    子线程到主线程的方法

    Handler的一个重要作用就是子线程进入主线程更新UI。
    Android中的多进程、多线程也提到过2种
    handler1
    Activity.runOnUiThread(Runnable);View.post(Runnable)/View.postDelayed(Runnable, long)。
    这2种方法其实就是Handler机制实现的。

    Activity.java
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
    
    View.java
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
    
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
    
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions( info.mHandler );
            mRunQueue = null;
        }
        ......
    }
    
    HandlerActionQueue.java
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
    
            mActions = null;
            mCount = 0;
        }
    }
    

    主线程的Looper

    ActivityThread中的main()是主线程的入口。
    从下面代码中可以看出来,应用启动 主线程默认创建了Looper,它是不可退出的。Looper有单独保存并获取主线程Looper的方法。
    主线程Looper创建参数为false(prepare(false)),即looper()的循环是不会停止的,当没有消息时,一直是阻塞的。

    Run|Debug
    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        /// M: ANR Debug Mechanism
        mAnrAppManager.setMessageLogger(Looper.myLooper());
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    Looper.java
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    

    ANR问题

    looper()死循环为什么没导致ANR?ANR具体什么造成?ANR和Looper有什么关系?
    ---这篇已经过长,这些问题在ANR部分总结更好。

  • 相关阅读:
    【集合】元组元素命名
    普通数组-队列
    稀疏数组
    Java基础 07 API概述 Scanner类 Random类 ArrayList类
    Java基础 06 类与对象、封装、构造方法
    Java基础 05 数组
    Java基础 04 IDEA、方法
    Java基础 04 [附] IDEA 的安装、配置与使用
    Java基础 03 流程控制语句
    Java基础 02 数据类型转换、运算符、方法入门
  • 原文地址:https://www.cnblogs.com/fanglongxiang/p/13285896.html
Copyright © 2020-2023  润新知