Android 性能优化(二)Handler运行机制原理,源码分析

Handler机制

Handler机制几乎贯穿了整个应用从启动到结束,包括Throwable抛出异常、View绘制、事件分发、Activity启动、Activity生命周期等都涉及到Handler机制。

我们知道,主线程中不建议耗时操作,子线程中不允许更新UI,会可能导致ANR。所以,我们需要,能在子线程中做完耗时操作,然后去到主线程更新UI的办法。消息机制,即消息的发送、入队、出队、分发过程。

Android 消息机制涉及了四个部分:

  • Handler: 消息的发送者和处理着
  • Message: 消息的载体
  • MessageQueue: 消息队列
  • Looper: 消息循环体

消息机制流程如下:

  • 主线程通过prepare创建一个Looper对象,且规定只允许关联一个Looper。
  • MessageQueue是依赖Looper一起创建的。Thread = Looper = MessageQueue。
  • 调用Looper.loop()方法,进行消息循环获取MessageQueue中的Message对象,如果消息为空就会进行线程阻塞。
  • 创建Handler对象后,自动绑定Looper对象和MessageQueue队列。
  • 然后调用与Message绑定的Handler对象的dispatchMessage方法实现接口回调,从而更新handle所在的主线程的UI。

Looper轮询器

public static final void main(String[] args) {
     //1、调用prepare()方法创建Looper对象并把该对象setMainLooper()绑定到当前线程中
        Looper.prepareMainLooper();
        ...
     //2、主线程中调用Looper.loop(),开始轮询,取消息
        Looper.loop();
    }}

Looper.prepareMainLooper()方法:

public static final void prepare() {
//保证当前线程只能存在一个Looper对象,如果已经存在就会抛出异常
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper());//把Looper绑定到当前线程
}
//Looper这个类的构造方法是private私有的,不允许外界直接new出来Looper对象。
private Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

请注意!MessageQueue是依赖Looper一起创建的。

Looper.loop()方法轮询消息:

   public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;
        while (true) {     
            Message msg = queue.next(); //1、messageQueue获取消息
            if (msg != null) {
                ...   
                msg.target.dispatchMessage(msg);// 2、handle 处理消息
                msg.recycle();  // 3、message回收
    }}}

 Handler 处理者

在构造方法中获取潞当前线程的Looper和MessageQueue。

public Handler() {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = null;
}
//handler.post(runnable) 、view.post(runnable)
<!--最后runnable都会封装成Message,放入队列-->

发送消息:send方法(post方法最终也会调用send方法)

public boolean sendMessageAtTime(Message msg, long uptimeMillis){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;//注意
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }else {
        RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue");
    }
    return sent;
}

处理消息:dispatchMessage、handleCallback

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            mCallback.handleMessage(msg)
            return;
        }
        handleMessage(msg);
    }
}
<!--分发调用-->
private static void handleCallback(Message message) {
    message.callback.run();
}

Message 和 MessageQueue

Message内部构建一个链表维护被回收的Message对象,当调用obtain方法时会优先从池中获取,如果池中没有则创建新的Message对象,同时在使用完毕之后,进入池中以便于复用。

obtain方法创建Message对象的流程:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

在Looper.loop()方法,使用完毕时候调用了Message的recycle()方法。 

MessageQueue主要包含两个操作:

  • 插入消息(enqueueMessage)
  • 读取消息(next)

插入消息(enqueueMessage):通过单链表(插入和删除效率高)的数据结构来维护消息列表。通过单链表的插入操作,根据时间看当前发送的Message是否需要马上处理。

触发时机:Handler发送消息的时候调用。

final boolean synchronized enqueueMessage(Message msg, long when) {//新消息msg
    Message p = mMessages;//当前消息头节点 p
    if (p == null || when == 0 || when < p.when) {
            msg.next = p;//队列为空||时间最小,插入到头部
            mMessages = msg;
            needWake = mBlocked; // 队列阻塞状态
    } else {
           Message prev = null;
           while (p != null && p.when <= when) {//延迟时间越大,排到队尾
                prev = p;
                p = p.next;
           }
           msg.next = prev.next;
           prev.next = msg;
           needWake = false; //无需唤醒
          }
    }
    if (needWake) { 
        nativeWake(mPtr);//唤醒
    }
    return true;
}

读取消息(next):采用阻塞的方式去获取消息队列中的消息,一旦有消息立即返回并且将它从单链表中移除;如果没有消息就一直阻塞。

final Message next() {
    int nextPollTimeoutMillis = 0;
    for (;;) {//阻塞线程for (;;)
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(mPtr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            final Message msg = mMessages;
            if (msg != null) {
                final long when = msg.when;
                if (now >= when) {//延迟时间 == 当前时间
                    mBlocked = false;
                    mMessages = msg.next;
                    msg.next = null;
                    return msg;
                } else {//取最小值:剩下延迟时间-循环阻塞
                    nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
        }
        dispose();
        return null;//Looper.quit(),返回null。
    }
}

Looper.loop()为什么不会阻塞主线程?

  • Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息, 然后将事件分发出去。

  • loop()里的消息队列里next中的耗时操作,本身并不会导致主线程卡死。导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。

IdHandler(闲时机制)?

是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当消息队列为空或延迟消息还未执行的时候,执行任务的一种机制。适用于启动优化的场景,将不必要的操作在页面绘制完成之后执行

postDelay()、post()与sendMessage()区别?

post方法一般用于异步操作,通过传入runnable对象处理逻辑。post方法最终也会调用send方法,将runnable封装成message对象,调用queue.enqueueMessage(msg, uptimeMillis)插入消息队列中后,根据延迟时间放在阻塞单链表中,只不过post延迟时间为0。sendMessage()一般用于多线程通信,在子线程调用该方法并传入自己创建Message对象实例,handle在主线程实现handleMessage接口回调,处理message携带的数据逻辑。


 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾阳Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值