• Android Handler 源码分析(详细)


    1、Android消息机制是什么?

    Android 消息机制 主要指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作流程。Handler 的主要作用是将任务切换到指定线程去执行,我们常用的就是通过 Handler 来异步更新 UI(线程间的信息传递)。

    2、Handler 使用方法

    2.1、创建一个 Handler 实例用来发送接收并处理消息

            Handler myHandler = new Handler() {
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case TestHandler.GUIUPDATEIDENTIFIER:
                            myBounceView.invalidate();
                            break;
                    }
                    super.handleMessage(msg);
                }
            };
    
            myHandler.post(run);
            myHandler.sendEmptyMessage(TestHandler.GUIUPDATEIDENTIFIER);
            myHandler.sendMessage(myHandler.obtainMessage());

    上面最后三行都是发送消息,你也可以在其他线程发消息,然后在主线程接收,处理 UI 逻辑;

    2.2、如果你需要在某个线程来处理消息,那也是可以的:

       new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();
                    mLooper = Looper.myLooper();
                    mHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                            Log.d(TAG, "handleMessage: " + msg.what);
                        }
                    };
                    Looper.loop();
                }
            }).start();

    这里 Looper 让该线程一直在运行,从而通过消息循环机制获取从其他线程发来的消息,传给 Handler 来处理。

    如下,你在下面的线程中发送一个消息,上面的 Handler 就会收到:

            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (mLooper != null) {
                        mHandler.sendEmptyMessage(123456);
                        Log.d(TAG, "sendMessage: " + 123456);
                    }
                }
            }).start();

    这样似乎也可以达到两个线程互相通信的效果。

    3、Handler 不正确使用引发的内存泄露

    容易造成内存泄漏的一种Handler使用方法:将 Handler 声明为 Activity 的内部类。在 Java 语言中,非静态内部类会持有外部类的一个隐试引用,这样就可能造成外部类无法被垃圾回收。而导致内存泄漏。

    那么正确的使用就是:

    • 将 Handler 声明为静态内部类。并持有外部类的若引用。
    • 在子线程中使用 Handler,这是需要我们自己创建一个 Looper 对象。

    具体可参考文章:Android 常见内存泄露 & 解决方案

    其中该文的第二种方法贴下,我也是第一次看到:

    将自定义 Handler 抽出去,也同样达到效果的小栗子:

    首先创建一个类,通过泛型将实例传入

    public class UIHandler<T> extends Handler {
        protected WeakReference<T> ref;
        public UIHandler(T cls){
            ref = new WeakReference<T>(cls);
        }
     
        public T getRef(){
            return ref != null ? ref.get() : null;
        }
    }

    看下 activity 中使用,直接用 myHandler 对象发送 message 即可。

    private static class UIMyHandler extends UIHandler<HandlerActivity>{
     
            public UIMyHandler(HandlerActivity cls) {
                super(cls);
            }
     
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                HandlerActivity activity = ref.get();
                if (activity != null){
                    if (activity.isFinishing())
                        return;
                    switch (msg.what){
     
                    }
                }
            }
        }
     
        private UIMyHandler myHandler = new UIMyHandler(this);

    其实两种写法大同小异,只是觉得第二种方式思想更好,这种思想值得每一个人学习。

    4、Handler 原理简析

    它在使用的过程中主要与 Messgae、MessageQueue、和 Looper 这三个对象关联密切,Handler 机制的实现原理依赖于这三者。下面就来简单讲讲这三者和 Handler 之间的关系。

    4.1 Handler :消息发送者,处理者,

    几个常见的构造方法,分别是:

    • Handler() 默认构造方法,与当前线程及其Looper实例绑定。如在主线程中执行new Handler(),那么该 handler 实例所绑定的便是 UI 线程和 UI 线程绑定的 Looper 实例。
    • Handler(Handler.Callback callback) 与当前线程及其 Looper 实例绑定,同时调用一个 callback 接口(用于实现消息处理——即在 callback 中重写 handleMessage() 方法)
    • Handler(Looper looper) 将该新建的 handler 实例与指定的 looper 对象绑定。
    • Handler(Looper looper, Handler.Callback callback) 指定该 handler 实例所绑定的 looper 实例并使用给定的回调接口进行消息处理。

    4.2 Message 信息传播的载体

    extends Object implements Parcelable

    一个 message 对象包含一个自身的描述信息和一个可以发给 handler 的任意数据对象。这个对象包含了两个int 类型的extra 字段和一个 object 类型的 extra 字段。利用它们,在很多情况下我们都不需要自己做内存分配工作。 

    虽然 Message 的构造方法是 public 的,但实例化 Message 的最好方法是调用 Message.obtain() 或 Handler.obtainMessage()(实际上最终调用的仍然是 Message.obtain() ,因为这两个方法是从一个可回收利用的 message 对象回收池中获取Message实例。该回收池用于将每次交给 handler 处理的 message 对象进行回收。 

    4.3 MessageQueue 信息传递的队列

    MessageQueue 是用来存放 Message 的集合,并由 Looper 实例来分发里面的 Message 对象。同时,message 并不是直接加入到 MessageQueue 中的, 而是通过与 Looper 对象相关联的 MessageQueue.IdleHandler 对象来完成的。我们可以通过 Looper.myQueue() 方法来获得当前线程的MessageQueue。 

    PS: MessageQueue 是由 Looper 来管理的,当你创建。

      public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

    4.4 Looper 消息处理的动力来源

    Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用 Looper.prepare() 方法来获取,并通过 Looper.loop() 无限循环地获取并分发 MessageQueue 中的消息,直到所有消息全部处理。

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    sThreadLocal 对象是一种特殊的全局性变量,它的全局性仅限于自己所在的线程,而外界所有线程一概不能访问到它,因此,每个线程的 Looper 是独立的,这也是如果我们想在一个线程中处理消息,必须先调用 Looper.prepare() ,是为了区别于线程来保存数据。

     /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        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));
        }

    可以发现,当调用 Looper.prepare() 会重新创建一个新的 Looper,这也说明了确实每个线程一个 Looper。

    当我们想要在其他线程传递数据到主线程时,一般会先获取主线程分 Looper,原因也是上面说的,每个每个线程一个 Looper。而 Looper 又是MessageQueue 的持有者,只有将消息放入到对应线程的 MessageQueue 中,才能在对应线程中做相应的处理。

    这也是 Handler 跨线程处理消息的本质所在。

    4.5 ThreadLocal 线程数据存储者

    上源码:

    public class ThreadLocal<T> {
     .....
    }

    这里可以看出 threadlocal 是一个范型类,这标志着 threadlocal 可以存储所有数据,作为存储数据来说,我们首先想到的是会对外提供set(), get(), remove(),等方法。 

    这里可以看下 set 方法,可以发现是先获取 Thread.currentThread;数据和线程是紧密联系在一起的。

    /**
         * Sets the value of this variable for the current thread. If set to
         * {@code null}, the value will be set to null and the underlying entry will
         * still be present.
         *
         * @param value the new value of the variable for the caller thread.
         */
        public void set(T value) {
            Thread currentThread = Thread.currentThread();
            Values values = values(currentThread);
            if (values == null) {
                values = initializeValues(currentThread);
            }
            values.put(this, value);
        }

    ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据。 可以理解为 hashmap,根据key值(所处的线程)来获取values. 其中 values 里面保存着各种各样的数据。它们对ThreadLocal所做的读写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。

    4.6 消息延时原理

    最后会调用 enqueueMessage 将 message 添加到单链表队列中。

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

    最终会调用 MessageQueue 的 enqueueMessage() 方法:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    接着我们再来看代码:

    boolean enqueueMessage(Message msg, long when) {
            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;
                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 {
                    // 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 (;;) {
                        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;
        }

    根据延迟时间的长短,先将 message 添加到单链表队列中去。如果添加到第一个的,就会调用 nativeWake 来唤醒当前的阻塞。这里大家可能没看到哪里阻塞了,后面会提到。接下来我们看 loop()  方法:

    /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            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);
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                    msg.target.dispatchMessage(msg);
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                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();
            }
        }

    从中可以看到会从 MessageQueue 队列中获取下一个消息,如果获取到了,就会调用 msg.target.dispatchMessage(msg) 来处理这个消息。那怎么去获取这个消息呢,接下来看看 next 的内容。

    Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                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) {
                        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;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // 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.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    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.
                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 {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                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;
            }
        }

    可以看到,在是在一个无线循环里面调用 nativePollOnce(ptr, nextPollTimeoutMillis) 进行阻塞。nativePollOnce() 的作用类似与 object.wait(),只不过是使用了 Native 的方法对这个线程精确时间的唤醒。如果时间是 0 会立马返回,也就是不会阻塞。接着会继续往下执行。

    如果是第一次运行,会优先处理 idlers,只有这个处理完了,才会开始处理队列里面的消息。然后会计算消息的延时,时间到了,就会return,回到 loop ()方法。

    继续处理下一个消息。

    总结:

    • postDelay() 一个10秒钟的 Runnable A、消息进队,MessageQueue 调用 nativePollOnce() 阻塞,Looper 阻塞;

    • 紧接着 post() 一个 Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把 B 插入消息队列的头部(A的前面),然后调用nativeWake() 方法唤醒线程;

    • MessageQueue.next() 方法被唤醒后,重新开始读取消息链表,第一个消息 B 无延时,直接返回给 Looper;

    • Looper 处理完这个消息再次调用 next() 方法,MessageQueue 继续读取消息链表,第二个消息 A 还没到时间,计算一下剩余时间(假如还剩9秒)继续调用 nativePollOnce() 阻塞;直到阻塞时间到或者下一次有 Message 进队;

    到此,关于Handler 的内容基本总结完毕。

    5、最后附上一张图:

    概括:子线程获取主线程的 Looper,然后发送 Message。 Message 被压入主线程 Looper 中的 MessageQueue,Looper 从队列中取出一个消息,交给主线程中的 Handler 来处理。需注意,发消息和处理消息的 Handler 同属于某个 Handler 的实例。

     参考文章

    1、android Handler机制之ThreadLocal详解

    2、深入理解Android中的Handler机制

    3、Android Handler正确使用姿势

  • 相关阅读:
    Android开发(十五)-Service和BroadcastReceiver
    Android开发(十四)-ContentProvider数据共享
    Android开发(十三)-图形与图像处理
    Android开发(十二)-Android应用资源
    Android开发(十一)-Intent和IntentFilter通信
    Android开发(十)-Activity和Fragment
    Android开发(九)-事件机制
    模拟面试
    二叉堆
    面试
  • 原文地址:https://www.cnblogs.com/huansky/p/9427854.html
Copyright © 2020-2023  润新知