Android的Handler原理

Handler是Android的消息机制,想必都熟悉和使用过。可能很多初学安卓的同学对报错:Only the original thread that created a view hierarchy can touch its views这个异常并不陌生,英文意思是只有创建视图层次结构的原始线程才能接触到它的视图,就是只能在主线程中更新UI,在子线程中更新UI就会报这个异常,有兴趣可以了解一下view的显示在屏幕过程中的原理,其中ViewRootImpl类就是负责View 的测量,布局,绘制操作,该类的requestLayout()方法请求view布局的时候就调用这个checkThread()方法判断当前线程是否是主线程。

问题:怎么在子线程中更新UI呢?

想必这个问题,做安卓开发的都会解决,可能都会异口同声的说使用Handler或者调用runOnUIThread方法可以切换到主线程中进行更新ui,是的,如果知道怎么使用后能知道原理,那后面使用的过程中也会避免一些问题,比如使用Handler不注意引起内存泄漏问题,了解原理能更巧妙的运用。其实Activity的runOnUIThread方法最终也是成员Handler对象切换线程的。

 一、消息机制简介

整个消息机制主要是由Handler、MessageQueue、Looper这三个类实现的,其中Message就是消息的包装类。Handler负责消息的发送处理,MessageQueue就是存储消息的队列,负责管理消息、堵塞挂起和换新线程,Looper负责轮询获取MessageQueue的消息进行分发,一个线程只能有一个Looper,线程中的成员ThreadLocal存储这个Looper,而一个线程可以有多个Handler。

以下是使用Handler的例子,下面例子的消息都是运行在一个子线程中,别错误当成主线程更新ui的例子,初学者注意了哈:

如果在一个Activity使用Handler,最好定义一个静态类继承Handler,这样创建这个静态Handler对象后就不会持有Activity的引用,因为我是用Kotlin写的demo,下图Kotlin的内部类默认就是静态的。

 线程中的Looper与Handler关联。如下图:

发送一个消息,消息就会分发到Handler的handleMessage方法中。

 

二、消息机制源码分析

1、Looper类源码分析

调用prepare方法,主要是创建Looper对象,并保存在线程私有的ThreadLocal本地存储区域,就是sThreadLocal成员变量中,线程之间无法访问彼此本地存储区域。

 然后调用loop()方法进入循环模式,循环直到消息为空时退出循环,读取MessageQueue的下一条Message,把Message分发给相应的target。当next()取出下一条消息时,队列中已经没有消息时,会产生阻塞。等待MessageQueue中加入消息,并重新唤醒线程。

public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列

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

    for (;;) { //进入loop的主循环方法
        Message msg = queue.next(); //可能会阻塞,因为next()方法可能会无限循环
        if (msg == null) { //消息为空,则退出循环
            return;
        }

        Printer logging = me.mLogging;  //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //获取msg的目标Handler,然后用于分发Message 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
         
        }
        msg.recycleUnchecked(); 
    }
}

2、MessageQueue源码分析

获取消息的next()方法:Looper.loop()方法中循环调用MessageQueue队列的next()方法获取消息,nativePollOnce方法是堵塞操作,该方法第二参数nextPollTimeoutMillis代表下一个消息来到前,还需要等待的时长,当nextPollTimeoutMillis为-1时,当前队列没有消息,会堵塞当前线程进入休眠状态。

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //当消息循环已经退出,则直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,为空则退出循环。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 获取一条消息,并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                }
            } else {
                //没有消息
                nextPollTimeoutMillis = -1;
            }
         //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
             if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                 pendingIdleHandlerCount = mIdleHandlers.size();
             }
             //如果队列没有消息,pendingIdleHandlerCount等于-1或者0,所有就执行下面语句
             if (pendingIdleHandlerCount <= 0) {
             // 没有消息就赋值mBlocked状态为true,并且此时nextPollTimeoutMillis为-1,
             //调用continue后循环一次调用nativePollOnce方法堵塞当前线程,后面如果有消息入列就会调用nativeWake方法唤醒线程
                  mBlocked = true;
                  continue;
                }
            ...............................
    }
}

进入队列的enqueueMessage方法:

MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

boolean enqueueMessage(Message msg, long when) {
    // 每一个Message必须有一个target
    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) {  //正在退出时,回收msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; 
        } else {
            //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
            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;
            //前一个节点指向要插入的节点,就完成的一个节点的插入操作
            prev.next = msg;
        }
        //队列如果为空,线程就属于等待状态,而且needWake值为true
        //队列如果不为空,队列会一直循环获取消息并处理或者堵塞等待nextPollTimeoutMillis时长唤醒操作。
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

3、Handler的源码分析

Handler是否负责把自身引用赋值给Message中的target,并且向队列中加入该Message消息,uptimeMillis是延时多长时间处理该消息

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //给Message的target赋值自身引用,后面消息分发时调用自身dispatchMessage方法分发消息
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //调用队列的入列方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }

//在loop()方法中,获取到下一条消息后,执行msg.target.dispatchMessage(msg),来分发消息到目标Handler对象。
分发消息流程:
当Message的msg.callback不为空时,则回调方法msg.callback.run();
当Handler的mCallback不为空时,则回调方法mCallback.handleMessage(msg);
最后调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。

消息分发的优先级:
Message的回调方法:message.callback.run(),优先级最高;
Handler中Callback的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1;
Handler的默认方法:Handler.handleMessage(msg),优先级最低。
public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //回调到实现Handler该方法中消费消息
            handleMessage(msg);
        }
    }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
       //其中mQueue是消息队列,从Looper中获取的
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //调用enqueueMessage方法
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Handler的消息机制图解:

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值