欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android消息机制——Handler

程序员文章站 2022-07-14 19:26:52
...


不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!

Handler机制

参考博客:

Android消息机制学习笔记

Android Handler消息机制原理最全解读(持续补充中)

Android消息机制全面解析

作用

主要是用于不同线程之间的通信。通常在子线程做完耗时操作后,通过handler切换为主线程执行UI操作。

对象

  • Handler

消息的发送和处理对象的控制器

  • Message

消息的载体

  • MessageQueue

存储消息的优先级队列

  • Looper

控制消息队列的循环

工作流程

  • app启动时,在ActivityThread的main方法中会调用Looper.prepare()方法,初始化looper并绑定主线程。looper对象内部还维护一个MessageQueue。
  • 刚开始,通过handler的sendMessage方法发送消息,然后调用MessageQueue.enqueueMessage方法向消息队列中加入消息。
  • 主线程调用Looper.loop方法遍历队列,用MessageQueue.next()取出消息。
  • 取出的消息不为空,那么调用msg.target.dispatchMessage()来分发消息。目标handler收到消息后,通过handler.handlerMessage()处理消息。

源码分析

  • 从子线程的sendMessage方法开始
//新建一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟耗时操作
                SystemClock.sleep(3000);
                //处理完耗时操作后,发送消息给主线程
                handler.sendMessage(new Message());
            }
        }).start();

Handler中的方法

  • 后面会发现,最终都调用了sendMessageDelayed方法
 public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }


    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        //从消息池取出消息,节省内存分配
        Message msg = Message.obtain();
        //设置消息的标识
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
  • 进入sendMessageDelayed方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        //推迟时间不合理
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

发现其调用了sendMessageAtTime方法,这个方法将延迟多少时间执行,变成了一个准确的执行时刻。

  • 进入sendMessageAtTime方法
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);
    }

拿到与handler关联的Looper中的消息队列,然后调用enqueueMessage方法将消息放入消息队列。

  • 进入enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将消息的目标指向handler
        //消息与handler关联
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //将消息插入队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }

将消息与它的目标handler关联,然后执行MessageQueue中的enqueueMessage方法,将消息插入队列。

MessageQueue中的方法

  • 基本只需要看一个方法,就是将消息插入消息队列
//向MessageQueue中插入这条消息
    //根据when的优先级队列,单链表数据结构
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            //消息是否有目标handler
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                //当前消息是否被使用
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) {
                //Handler所在线程是否存活
                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;
            //链表为空,或者时间是0,立刻执行,或者执行时间比表头执行时间早
            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) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在消息队列中加入消息前,需要判断:

  1. 这条消息是否有关联的handler。
  2. 这条消息是否已经被使用。
  3. 这条消息所关联的handler所在的线程是否存活。

当这三个条件都满足的时候,才可以插入消息,否则报错。

插入消息也有两种情况:

  1. 在表头插入消息

当这个消息队列为空;或者when等于0,意味着立刻执行;或者需要插入的消息的when小于表头消息的when,那么就在表头插入这条消息。

  1. 在适当位置插入消息

适当位置插入,那么就得先找到这个适当位置。从头开始,死循环遍历消息队列,当到了消息队列的最后或者找到了一个消息的when大于这条消息的when,那么就在这个消息的前面插入即可。

过渡

通过handler发送了消息,并且也通过MessageQueue插入消息到消息队列中了,接下来,就该从队列中取出消息进行处理了,所以需要循环消息队列,需要Looper的loop方法。

Looper中的方法

  • Looper的作用就是循环消息队列,看loop方法
//开启Looper
    public static void loop() {
        //返回当前线程的Looper
        final Looper me = myLooper();
        //当前线程没有looper,报错
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        //返回和当前线程关联的looper中维护的消息队列
        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        //无限循环,死循环
        for (;;) {
            //不断从队列取消息
            Message msg = queue.next(); // might block
            //到了末尾,队列中没有消息需要处理了
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }private Handler handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    textView.setText("对UI进行操作");
                }
            };
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            
            //msg.target的dispatchMessage传递消息,即就是handler的这个方法
            try {
                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);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
  • 由于loop方法太长了,截取一些核心代码进行查看
//开启Looper
    public static void loop() {
        //返回当前线程的Looper
        final Looper me = myLooper();
        //当前线程没有looper,报错
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        ......
        //返回和当前线程关联的looper中维护的消息队列
        final MessageQueue queue = me.mQueue;
        ......
        //无限循环,死循环
        for (;;) {
            //不断从队列取消息
            Message msg = queue.next(); 
            //到了末尾,队列中没有消息需要处理了
            if (msg == null) {
                return;
            }
            ......
            //msg.target的dispatchMessage传递消息,即就是handler的这个方法
            try {
                //msg.target就是指向与这个消息关联的handler,所以通过Handler的dispatchMessage方法进行消息传递
                msg.target.dispatchMessage(msg);
                ......
            //将处理后需要回收的消息放入消息池缓存
            msg.recycleUnchecked();
        }
    }

回到Handler中

  • 通过Handler.dispatchMessage去传递消息
//传递消息
    public void dispatchMessage(@NonNull Message msg) {
        //若消息的callback不空
        if (msg.callback != null) {
            //调用这个方法
            handleCallback(msg);
        } else {
            //自己的callback是否空
            if (mCallback != null) {
                //不空,传给自己的callback
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //实在不行,调用自己的handleMessage方法
            handleMessage(msg);
        }
    }

从源码中可以看出,若是有Callback接口,那么就会直接回调handleCallback方法。没有的话,只能调用自定义的handleMessage方法去处理消息了。

补充

Handler中的内存泄露问题

出现原因

  1. 非静态内部类导致的内存泄漏。
  2. MessageQueue中还有没有处理完的message,message的target即handler对Activity仍然持有引用,会导致内存泄漏。

解决方案

  1. 使用静态内部类 + 弱引用解决。
  2. 在销毁Activity时,清除其绑定的looper中维护的MessageQueue中的所有消息。

Handler中的Message对象的获取问题

问题:在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?

答:使用obtain创建对象是从消息池中取出Message对象,而new一个新对象,需要为其单独分配一块新的内存,这样可以节省内存的开销。

注意点

  • 一个线程只能有一个Looper,内部维护着一个唯一的MessageQueue,但是可以有多个Handler。

  • 创建的Looper都会保存在ThreadLocal(线程本地存储区)中。它不是线程,是一个内部存储类。作用是获取当前线程的looper。它可以在不同线程间互不干扰的存储和提供数据。

  • MessageQueue是一个时间优先级队列,但其内部是使用单链表的形式存储,根据时间,将消息插入队列中,表头的先出。

  • 通过Looper取出消息,然后通过handler分发消息到handler所在的线程,即进行了不同的线程间通信。

面试问题

  1. 为什么一个线程只有一个Looper、只有一个MessageQueue?

  2. 如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)

  3. 是不是任何线程都可以实例化Handler?有没有什么约束条件?

  4. Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

  5. 消息循环(死循环)的必要性:

  6. looper.looper()阻塞会不会消耗大量的cpu资源

  7. Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

  8. sendMessageDelayed是如何实现延时发送消息的?

  9. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?