Handler的原理分析

Handler的原理分析:

首先了解一下handler的主要成员

1.主要的有Message、MessageQueue、Looper、Handler

一个线程绑定一个looper,一个looper维护着一个MessageQueue队列,而一个线程可以对应多个handler

  • Handler:发送和接收消息
  • Looper:取消息并分发消息给handler
  • Message:将发送的消息封装成Message对象
  • MessageQueue:将发送的Message对象存储在MessageQueue队列中

下面我们跟着问题一步一步来了解handler吧

Handler.sendMessageDelayed()怎么实现延迟的?

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;
        //1.当前头节点是否为空
        //2.存储的消息延迟时间是否是0
        //3.存储的消息时间是否小于头节点的时间
        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 = p.next;
                //如果下一个节点为空或者插入的消息处理时间小于下一个节点的时间
                //则找到插入的位置并跳出循环
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //将消息的next指向下一个节点
            msg.next = p; // invariant: p == prev.next
            //将上一个节点指向msg,插入成功
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

1.handler在发送消息的时候最终调用的是sendMessageAtTime方法

这个方法又调用MessageQueue的enqueueMessage(),队列是一个链表的数据结构

首先拿到消息队列的头节点,首先判断

p == null || when == 0 || when < p.when

这三个中如果有一个成立,那么就采用头插法,将该消息插入,并立即唤醒队列

否则的话,遍历找到链表,按照延迟时间大小进行插入,越靠后的节点,延迟时间越长,这样就形成了一个有序的链表。

 

接着在MessageQueue中的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
        //一直阻塞不会超时
       //nextPollTimeoutMillis ==-1
       //不会阻塞,立即返回
       //nextPollTimeoutMillis == 0 
       //最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
       //nextPollTimeoutMillis > 0 
        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;
                msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
                如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
                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
                    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 {
                        //否则
                        mBlocked = false;
                        //如果上一个消息不为空(刚开始初始化为空,取出异步消息)
                        if (prevMsg != null) {
                        //   将prevMsg的next指向当前msg的下一个节点
                            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;
                }

                // 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.
                //刚开始pendingIdleHandlerCount为-1 
                //且如果消息队列为空
                //或者当前消息的发送时间大于当前时间,也就是延时消息
                //会把idleHandler集合的大小赋值给pendingIdleHandlerCount 
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //如果小于零则continue
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                //将mIdleHandlers集合转化为数组
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //遍历该mPendingIdleHandlers数组   
            //执行回调方法queueIdle()
           //返回值如果为false为一次性后期会mIdleHandlers.remove(idler);
           //ture可复用
            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;
        }
    }

为什么 Handler 能够切换线程执行?

Handler 发送的线程不处理消息,只有Looper.loop()将消息取出来后再进行处理,所以在Handler机制中,无论发送消息的Handler对象处于什么线程,最终的处理都是运行在 Looper.loop() 所在的线程。

比如:一个新的线程 Thread1 发送了一个消息 Msg1,这个线程的工作仅仅是将消息存储到消息队列而已,并没有下一步了,然后等待 Looper.loop() 处理到 Msg1 的时候(loop()方法一直运行在最开始调用它的线程,比如主线程),再将 Msg1 进行处理,所以最终就从 Thread1 切换到了主线程中运行。

Handler.post(Runnable) 方法是运行在新的线程吗?

//Handler.java 
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

...

几个 post 方法都是调用了相应的sendXXX 方法,然后用getPostMessage(Runnable r) 构建 Message 对象:

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

这里获取到消息后,将 Runnable赋值给 Message.callback ,那这个 callback 有什么用呢?上面的整体流程分析中,我们知道 Looper.loop()会调用 msg.target.dispatchMessage(msg),这个target 就是 Handler 了,那么看一下这个方法的具体实现:

    /**
     * Handle system messages here.
     */
    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();
    }

到这一步水落石出,如果是用 postXXX 方法发送的消息,就会调用 handleCallback(msg) 方法,即调用我们post方法里传递的 Runnable 对象的run()方法。

也就是说,Runnable 跟线程没有半毛钱关系,他只是一个回调方法而已。

为什么创建 Message 对象推荐使用 Message.obtain()获取?

Message 对象有两种方式可以获得,一种是直接 new 一个实例,另一种就是调用 Message.obtain()方法了

Handler.obtainMessage() 也是调用Message.obtain()实现的,看看这个方法:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

可以看到,如果消息池不为空,obtain() 方法会将头结点 sPool取出,并置为非使用状态,然后返回,如果消息池为空,则新建一个消息。

知道有消息池这个东西了,那么这个消息池的消息是怎么来的呢?

使用 AS 搜索一下,发现只有两个方法对 sPool 这个节点进行了赋值,一个是上面的 obtain(),另一个是下面这个:

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                //这里!!
                sPool = this;
                sPoolSize++;
            }
        }
    }

看方法名也可以知道,这是一个回收的方法,方法体内将 Message 对象的各种参数清空,如果消息池的数量小于最大数量(50)的话,就当前消息插入缓存池的头结点中。

已经知道 Message 是会被回收的了,那么什么情况才会被回收呢?

 

// Looper.java ,省略部分代码
loop(){
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block  , 从队列取出一个msg
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        msg.target.dispatchMessage(msg); //Handler处理消息
        ...
        msg.recycleUnchecked();  //回收msg
    }
}

其中的一个调用是在 Looper.loop()方法中,调用时机是在 Handler 处理事件之后,既然是 Handler 处理后就会回收,那么如果在 Handler.handleMessage() 中用新的线程使用这个 msg 会怎样呢?

//MainActivity.java
@SuppressLint("HandlerLeak")
static Handler innerHandler = new Handler() {
    @Override
    public void handleMessage(final Message msg) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean isRecycle = msg.obj == null;
                Log.e("====是否已经回收===", "" + isRecycle);
            }
        }).start();
    }
};

private void send(){
    Message msg = Message.obtain();
    msg.what = 0; //标识
    msg.obj = "这是消息体"; //消息内容
    innerHandler.sendMessage(msg);
}

当调用 send()方法发送消息后,发现打出 log:

E/====是否已经回收===: true

也就说明我们的推断是正确的。所以在平时使用中,不要在 handleMessage(Message msg)方法中对 msg 进行异步处理,因为异步处理后,该方法会马上返回,相当于告诉 Looper 已经处理完成了,Looper 就会将其回收。

如果真要在异步中使用,那么可以创建一个新的 Message 对象,并将值赋值过去。

总而言之,因为 Handler 机制在整个 Android 系统中使用太频繁,所以 Android 就采用了一个缓存策略。就是 Message 里面会缓存一个静态的消息池,当消息被处理或者移除的时候就会被回收到消息池,所以推荐使用 Message.obtain()来获取消息对象。

Handler内存泄漏分析:

首先如果此时activity被销毁,此时而在主线程一直运行的looper持有handler的引用,创建handler是非静态内部类,内部类会持有外部类的引用,也就是handler持有activity的引用,此时activity不会回收,造成内存泄露,looper持有handler引用,是handler在发送消息的时候,sendEmptyMessage()方法中会将handler赋值给msg.target,这样如果message对象不被销毁,handler对象就会一直存在。而此时message会被存放到MessageQueue消息队列中,直到Looper取出交给handler处理后,才会被释放掉。处理方式:静态内部类并使用弱引用。

IdleHandler的调用时机相关描述应该为:

每次消费掉一个有效message,在获取下一个message时,如果当前时刻没有需要消费的有效(需要立刻执行)的message,那么会执行IdleHandler一次,执行完成之后线程进入休眠状态,直到被唤醒

IdleHandler一些用法

  1. Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
  2. 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
  3. 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
  4. 一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看

https://blog.csdn.net/luoshengyang/article/details/6817933

:Android应用程序消息处理机制(Looper、Handler)分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值