Android重修课 -- Handler机制

1. 什么是Handler

什么是Handler机制,是Android中系统提供的一套线程间通信机制,大部分情况下用于子线程和UI线程通信。

2. Android中需要线程通信的背景

Android的主线程是不允许做耗时操作的,如果在UI线程进行长时间的耗时操作,界面会卡住。卡顿的时间过长,就会ANR。

然而我们有些操作就是需要一些时间才能完成,而且我们后续的操作就是需要依赖这个结果,比如网络请求,复杂的运算等。对于这些不确定时长的操作(有可能消耗很长的时间),我们需要放到其他线程中去。

那么随之而来就有一个问题了,我在这个工作线程完成耗时操作,这个时候主线程怎么能感知到呢?这里就需要用到跨线程的通信了。

3. java中线程间通信常用手段

我们知道,在java中,最常用的线程间通讯的方法就是共享变量的方式。

同一个变量或者说对象在内存中可以被多个线程访问到,进行各种操作。然后通过synchronized或者waitnotify的等方式来保证不同线程对同一对象进行操作,而不会产生线程安全问题。

4. Android中使用Java线程间通信的基础

在Android的Handler机制中,MessageQueue就是这样一个对象,它维护的消息队列可供多个线程访问,修改。

顾名思义,MessageQueue是一个用于储存消息的队列数据结构。

既然是队列,那么就一定有入队和出队操作,我们在MessageQueue类中可以找到enqueueMessage()next()方法,这两个方法就是封装了入队和出队操作。

源码如下:

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对象
        Message p = mMessages;
        // 是否需要wake
        boolean needWake;
        // 当前消息为空,入队时间为0,或者入队时间小于当前消息的处理时间
        if (p == null || when == 0 || when < p.when) {
            // 说明需要入队的消息是在队列中需要第一时间处理的,那么放入对象的最前面
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 这种情况是需要插入到队列中间或者末尾的情况
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 根据消息需要处理时间when的大小,排好队列,插入到适当位置
            for (;;) {
                // 记录遍历节点的前一节点
                prev = p;
                // 把p指向p的下一个节点
                p = p.next;
                // 如果p已经是队尾节点 或者 该消息的处理时间在p的处理时间之前
                if (p == null || when < p.when) {
                    // 那么去插入当这个位置
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            // 插入消息节点
            msg.next = p; 
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码
Message next() {
    // ...
    for (;;) {
        // ... 

        // 同步代码块,  查找下一个需要被处理的消息,返回回去
        synchronized (this) {
            // 获取当前时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 不断的查询下一个需要被处理的消息
                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 {
                    // 找到需要现在处理的消息 msg
                    mBlocked = false;
                    // 把队列的标志从当前消息节点移到它的next节点
                    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;
            }

            // ... 
        }

        // ... 
    }
}
复制代码
5. 了解Message

在源码中,我们可以看到,消息队列是对Message数据进行操作,我们了解一下Message

Message 是一个实现了Parcelable的类,承载着信息在不同的线程中传递。对于Message中的whatarg1arg2data这些承载具体数据的载体,可以去源码中查阅。

这两个方法就是消息队列的入队和出队操作。有了MessageQueue,我们就有了可以支持多线程访问修改消息队列的基础了。那么这个队列又是被哪些类使用了?

在Message中还有个字段是target,类型是Handler,指定此消息是归属在哪个Handler的处理范围内。那我们看看Handler是如果处理Message的。

6. Handler如何将Message放到MessageQueue中

Handler是承担着处理和发送Message的重担。

我们平常使用Handler的时候,通过Handler的实例对象调用sendMessage()sendMessageDelayedpost(Runnable)之类的,最终都是将一个Message的实例对象通过enqueueMessage()方法放入到MessageQueue中。

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

最终是通过MessageQueue的enqueueMessage()方法将Message放入到MessageQueue中去,那这个queue对象是哪里来的呢,我们全局搜索一下,可以再Handler构造方法中找到答案。

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
复制代码
7.Handler与Looper的关联

可以看到,Handler中的MessageQueue是拿的Looper中的MessageQueue,那么Looper中的MessageQueue又是怎么来的呢,翻到Looper的源码中一看,在构造方法中有new出MessageQueue对象。

但是我们仔细一看,构造方法是private修饰的呢,那么Looper啥时候初始化呢,看看去,就在prepare()方法中调用。

这里用到了ThreadLocal类,ThreadLocal是一个可以创建线程局部变量的类,通过使用ThreadLocal创建的变量只能被该线程访问到,其他线程是无法访问的。

我们可以把sThreadLocal看成一个map,存放的是以Thread为key,以Looper为value的数据。我们可以以线程为key去获取是否有初始化过Looper。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

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对象。

8. Looper如何处理MessageQueue

那么对于Looper是如何进行对Message的处理操作呢,我们可以再Looper的loop()方法中找到答案

public static void loop() {
    // 获取当前线程的Looper
    final Looper me = myLooper();
    // 如果Looper为空,说明Looper没有在该线程初始化
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 拿到Looper中的MessageQueue
    final MessageQueue queue = me.mQueue;
    
    // ...

    // 不断从MessageQueue去拿消息
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // ...
        try {
            // 如果有需要处理的消息,则通过消息对应的Handler来处理
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        // ... 
        msg.recycleUnchecked();
    }
}
复制代码

loop()方法中,获取到当前线程的Looper对象,从Looper对象中拿到MessageQueue,然后不断去轮询队列,有消息就通过Handler的dispatchMessage()调用handleMessage(),然后就回到了我们需要处理消息的地方了。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码
9. 总结

所以整个流程如下过程:

  1. 在线程启动的时候,会通过Looper.prepare()方法初始化该线程的Looper对象,以及Looper对象中的MessageQueue
  2. 然后在Handler在初始化的时候,就与该线程的Looper关联上了,然后Handler在其他线程通过sendMessage()Message放入到关联Looper的MessageQueue中,这一系列操作都是与sendMessage()处于同一线程。
  3. 与此同时,Looper所在的线程中,Looper是一直在loop()方法中轮询着MessageQueue的,别的线程在放入消息之后,Looper就会拿出消息来交由Handler处理,这一系列操作发生在Looper所在线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值