Android事件驱动Handler-Message-Looper解析

前言

关于这方面的知识,网上已经有很多。为啥这里还要写呢?知其然,也要知其所以然,handler-message-looper的设计很巧妙,我们了解为啥要这么设计,对我们设计程序的时候也有裨益。这篇文章阐述怎么结合现象与原理分析事件驱动的本质,相信即使没看过相关知识的同学也能快速理解。
通过这篇文章你将知道:

1、如何进行线程切换
2、消息循环原理
3、子线程消息循环
4、链表实现栈和队列的

线程切换

  • Q1 为什么需要线程切换

1、多线程能够提高并发效率,多线程的引入势必会有线程切换
2、多线程同时引入了线程安全问题,Android为了安全、简单地绘制UI,规定绘制UI操作只能在一个固定的线程里操作,该线程在app启动时创建,我们称之为UI线程(也称做主线城,后续简单起见,统一叫做主线城)。非主线程我们称之为子线程,当我们在子线程里进行耗时操作,并准备好需要展示的数据时,这时候需要切换到主线程更新UI
线程安全问题请移步真正理解Java Volatile的妙用

  • Q2 如何切换线程
    先看如何创建并启动一个线程
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doTask1();
                    }
                });
                t1.start();

                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doTask2();
                    }
                });
                t2.start();

如上所示,t2需要等待t1执行完毕再执行,那么t2怎么知道t1完毕呢?有两种方式

1、t1主动告诉t2自己已执行完毕,属于等待通知方式(wait&notify、condition await&signal)
2、t2不断去检测t1是否完毕,属于轮询方式

假设t1是子线程,t2是主线程,那么t1采用何种方式通知t2呢?采用第一种方式:等待-通知,通过Handler-Message-Looper实现等待-通知。

Handler发送Message

经典例子

    Button button;
    TextView tv;

    private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
            return false;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.start_thread_one);
        tv = findViewById(R.id.tv);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(()->{
                    Message message = Message.obtain();
                    message.arg1 = 2;
                    handler.sendMessage(message);
                }).start();
            }
        });
    }

主线程里构造handler,子线程里构造message,并使用handler将message发送出去,在主线程里执行handleMessage方法就可以更新UI了。很明显,使用handler来切换线程我们只需要两步:

1、主线程里构造handler,重写回调方法
2、子线程里发送message告知主线程执行回调方法

看到此,你可能疑惑的是:主线程怎么就知道执行handleMessage方法?既然子线程发送了message,那么主线程应该有个地方接收到该message啊?看来信使是message,接下来进入源码一探究竟。Android Studio关联源码请移步:Android Studio关联Android SDK源码(Windows&Mac)

Message分析

既然message充当信使,那么其应当有必要的字段来存储携带的数据、区分收发双方等,实际上message不只有这些字段。上个例子里,我们通过静态方法获取message实例。

    private static Message sPool;
    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();
    }

sPool是静态变量,从名字可以猜测出是message存储池。obtain()作用是:先从存储池里找到可用的message,如果没有则创建新的message并返回,那什么时候将message放到存储池里呢?我们只需要关注sPoolSize增长的地方即可,搜索找到:

    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里有个message类型的next字段,熟悉链表的同学应该知道这是链表的基本操作,该字段用于指向链表里下一个节点的。现在要将一个message对象放入message存储池里:

将待存储的message next字段指向存储池的头部,此时该头部指向的是第一个元素,再将存储池的头指向待插入的message,最后将存储池里个数+1。这时候message已经放入存储池里,并且当作存储池的头。

我们再回过头来看看怎么从存储池里取出message:

声明message引用,该引用指向存储池头部,将存储池头部指向链表下一个元素,并将message next置空,并将存储池里个数-1,这时候message已经从存储池里取出。

可以看出,每次将message放入链表头,取的时候从链表头取,这不就是我们熟悉的栈结构吗。也就是说,message对象存储池是通过栈来实现的。

问题1

至此,我们分析了message存储池的结构,还留了两个问题后面分析:

1、我们通过obtain()获取message,那么啥时候调用recycleUnchecked()放入存储池呢?
2、可以直接构造message吗?

MessageQueue

从handler.sendMessage(message)分析:

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

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

sendMessageAtTime()方法输入两个参数,一个是待发送的message对象,另一个是该message延迟执行的时间。这里引入了一个新的类型:MessageQueue,其声明了一个字段:mQueue。接着调用enqueueMessage,该方法里msg.target = this,target实际上指向了handler。最后调用queue.enqueueMessage(msg, uptimeMillis),我们来看看queue.enqueueMessage方法:

        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);
            }
        }

MessageQueue顾名思义:Message队列。它是怎样实现队列的呢?里有个Message类型的mMessages字段,看到这是不是想到了前面的message存储池(栈)呢?没错,MessageQueue是通过message链接成链表的,只不过该链表是实现了队列的,接下来我们看看怎么实现队列。
enqueueMessage源码里标记了第一点、第二点、第三点三个关键点。

第一点

队列为空、或者延迟时间为0、或者延迟时间小于队列头节点,那么将该message next字段指向队列头,并将队列头指向该message。此时该message已经插入enqueueMessage的队列头。

第二点

如果第一点不符合,那么message不能插入队列头,于是在队列里继续寻找,直至找到延迟时间大于message的节点或者到达队列尾,再将message插入队列。

从第一点和第二点可以看出,enqueueMessage实现了队列,并且是根据message延迟时间从小到大排序的,也就是说队列头的延迟时间是最小的,最先需要被执行的。

第三点

问题2

唤醒MessageQueue队列,后续分析。

什么时候取出message

至此,handler.sendMessage(message)发送message到MessageQueue流程已经分析完毕,那么MessageQueue里的message什么时候取出来呢?
既然MessageQueue里有入队方法enqueueMessage,试着找找是否存在出队方法,很遗憾没有。那么我们有理由相信可能在某个地方操作了MessageQueue mMessages字段,查找后发现引用的地方蛮多的,不好分析。换个思路,想想MessageQueue对象mQueue在哪里被引用了?
image.png
定位到Handler构造函数

    public Handler(@Nullable 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;
    }

我们发现mQueue指向了mLooper里的mQueue,而mLooper = Looper.myLooper()。现在我们将怀疑的目光瞥向了Looper,Looper是何方神圣呢,接下来继续分析。

Looper分析

从Looper.myLooper()开始分析

##Looper.java
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

##ThreadLocal.java
   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可以看出,Looper对象存储在当前线程里,我们暂时不管ThreadLocal原理,看到get方法,我们有理由相信有set方法,果不其然。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

查找其引用,该方法引用的地方很多。既然ThreadLocal get方法在Looper.java里调用,那么ThreadLocal set方法是否也在Looper.java里调用呢?
image.png)

Looper.java
    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));
    }

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

查找prepareMainLooper()方法引用
image.png)
看到了我们熟悉的ActivityThread,也就是我们app启动main方法所在的类。
image.png)
已经追溯到源头了,我们现在从源头再回溯。

    private static Looper sMainLooper;  // guarded by Looper.class
    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));
    }

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

在prepare方法里,构造了Looper对象。此刻我们看到了主角mQueue被构造出来了,也就是说主线程启动的时候就已经构造了Looper对象,并存储在静态变量sThreadLocal里。同时静态变量sMainLooper指向了新构建的Looper对象。
接着查找mQueue的引用,发现
image.png
final MessageQueue queue = me.mQueue,此处引用了mQueue,接着调用了MessageQueue里的next()方法,该方法返回message对象。

MessageQueue.java
next()方法
                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;
                }

Message msg = mMessages,我们之前分析了handler发送message后最终是将message挂在mMessages上,也就是我们的message队列。而next方法后续操作是:从队列头取出message,并将该message从队列里移除。此时我们已经找到message队列入队时机与方式、message队列出队方式。那么message队列出队时机呢?也就是啥时候调用的。继续回溯分析上面提到的Looper loop()方法。
image.png
loop()方法简化一下

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);
            }
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            msg.recycleUnchecked();
        }

明显的看出,loop()方法里有个死循环,循环退出的时机是队列里没有消息或者其它异常。

msg.target.dispatchMessage(msg);msg.target就是我们之前提到的handler,也就是handler.dispatchMessage(msg)

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);(1)
        } else {
            if (mCallback != null) {
                (2)
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            (3)
            handleMessage(msg);
        }
    }

这方法里就是具体执行收到message的方法,我们发现里边还分了三个方法:

1、handleCallback()调用的是message里Runnable类型的callback字段,我们经常用的方法view.post(runnable)来获取view的宽高,此处runnable会赋值给callback字段。
2、handler mCallback字段,Callback是接口,接口里有handleMessage()方法,该方法的返回值决定是否继续分发此message。mCallback可选择是否实现,目的是如果不想重写handler handleMessage()方法,那么可以重写callback方法。
3、handleMessage,构造handler子类的时候需要重写该方法。

可能上述1、2、3点应用场合还造成一些疑惑,我们用例子来说明。
1、handleCallback()

                    //延时执行某任务
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            
                        }
                    }, 100);
                    
                    //view 获取宽高信息
                    tv.post(new Runnable() {
                        @Override
                        public void run() {
                            
                        }
                    });

此种方式,只是为了在某个时刻执行回调方法,message本身无需携带附加信息
2、mCallback.handleMessage(msg)
构造Handler匿名内部类实例

    private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
            return false;
        }
    });

3、handleMessage(msg)
内部类继承Handler,重写handleMessage方法

    class MyHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            tv.setText(msg.arg1 + "");
        }
    }
    
    MyHandler handler = new MyHandler();

此时,我们已经知道MessageQueue里的message执行时机。你可能还有疑惑,怎么就确保上述的runnable和handleMessage在主线程里执行呢?根据调用栈:
loop()->msg.target.dispatch()->runnable/handleMessage(),而loop是在main方法里调用的,该方法是app入口,是主线程。
我们还注意到loop()里有两条打印语句:

            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

可以看到位于dispatchMessage()前后,这给我们提供了一个便利:通过监测语句的输出,计算主线程执行message耗时。

loop()里的msg.recycleUnchecked(),现在来回答问题1

1、message通过obtain()从存储池里获取实例,在loop()里message被使用后,调用msg.recycleUnchecked()放入存储池里(栈)。
2、message对象不仅可以通过obtain()获取,也可以直接构造。最终都会放入存储池里,推荐用obtain(),更好地复用message。

MessageQueue next()分析

前面我们简单过了一遍next()方法,该方法是从MessageQueue里取出message对象。loop()通过next调用获取message,如果message为空则loop()退出,假设这种情况出现,那么主线程就退出了,app就没法玩了。因此从设计上来说,next返回的message不能为空,那么它需要一直检测message直到获取到有效的message,所以next就有可能阻塞。实际上,next使用的一个for循环,确实可能会阻塞,我们来看看细节之处:

        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;
                }
            }
        }

源码里标记了6个关键点,一一分析一下。

1、nativePollOnce(ptr, nextPollTimeoutMillis),查找native层MessageQueue是否有数据。nextPollTimeoutMillis=0 该方法立即返回;nextPollTimeoutMillis=-1;该方法一直阻塞直到被主动唤醒;nextPollTimeoutMillis>0;表示阻塞到特定时间后返回。现在我们来解答之前的问题2,当有message入队列的时候,调用如下代码:

            if (needWake) {
                nativeWake(mPtr);
            }

而needWake=mBlocked,当阻塞的时候mBlocked=true,执行nativeWake(mPtr)后,nativePollOnce()被唤醒返回。这就是最初我们为什么说handler-looper 采用等待通知方式。

2、如果队头节点是屏障消息,那么遍历队列直到找到一条异步消息,否则继续循环直到nativePollOnce阻塞。此处的应用场景之一是:在对view进行invalidate时候,为了保证UI绘制能够及时,先插入一条屏障消息,屏障消息delayTime=0,因此插入队列头部(注意和普通消息插入不同的是:如果队列某个节点和待插入的延迟时间一致,那么待插入的节点排在已有节点的后面,而屏障消息始终插在前面)。插入屏障消息后,后续的刷新消息设置为异步,再插入队列,那么该条消息会被优先执行。
3、如果还未到执行时间,则设置阻塞超时时间。
4、取出message节点,并调整队列头。
5、队列里没有message,那么设置阻塞
6、设置阻塞标记位mBlocked=true,并继续循环

Handler线程切换本质

上面经handler发送message,到message被消费整个流程已经剖析完毕。为了更直观了解流程,使用流程图表示。

message对象存储池

入栈
image.png
出栈
image.png

MessageQueue队列

出入队
image.png

调用栈

发送消息调用栈

handler.sendMessage(message)
sendMessage->sendMessageDelayed(msg, 0)->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)->enqueueMessage(queue, msg, uptimeMillis)->queue.enqueueMessage(msg, uptimeMillis)

取消息调用栈

msg = loop()->queue.next()
msg->dispatchMessage(msg)->handleCallback(msg)/handleMessage(msg)

将Handler、MessageQueue、Looper用图表示如下:
image.png
由于MessageQueue是线程间共享对象,因此对队列的操作(入队、出队)需要加锁。

Handler和Looper联系

怎么确保handleMessage(msg)在主线程执行呢?换句话说收到消息后是怎么切换到主线程的?

1、从取消息调用栈可知,执行loop()的线程就是执行handleMessage(msg)的线程,而loop()在main()方法里执行,因此这时候取出的消息就在主线程执行
2、Handler发送的message存储在Handler里的messagequeue,而此队列指向的是Looper的messagequeue,loop()方法从Looper的messagequeue里取消息。因此Handler和Looper共享同一个queue,handler负责发送(入队),Looper负责收取(出队),通过中间桥梁messagequeue实现了线程切换。
3、接着第二点,Handler和Looper是如何共享queue呢?在构建Handler的时候,会根据当前线程获取存储的looper并获取其messagequeue,而looper对象需要在同一线程里构造,并将该looper引用设置到当前线程。在app启动的时候,系统已经在主线程的looper并赋值给静态变量sMainLooper,我们可以通过getMainLooper()获取主线程的looper对象。
4、handler构造时,可以选择是否传入looper对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为handler没有和looper建立起关联。

子线程消息循环

以上,我们分析的是子线程发送消息,主线程在轮询执行。那我们能够在主线程(其它线程)发送消息,子线程轮询执行吗?这种场景是有现实需求的:

A线程不断从网络拉取数据,而数据分为好几种类型,拿到数据后需要分类处理,处理可能比较耗时。为了不影响A线程效率,需要另起B线程处理,B线程一直在循环处理A拉取回来的数据。

是不是似曾相识,我们想要B线程达到类似主线程的looper功能,接下来看看该怎么做。

            Handler handlerB = null;
            Thread b = new Thread(new Runnable() {
                @Override
                public void run() {
                    //子线程里创建looper对象,并保存引用到当前线程里
                    Looper.prepare();
                    
                    //Handler构造方法默认获取当前线程的looper引用
                    handlerB = new Handler(new Callback() {
                        @Override
                        public boolean handleMessage(@NonNull Message msg) {
                            return false;
                        }
                    });
                    //子线程开启loop()循环
                    Looper.loop();
                }
            });
            b.start();
            
            Thread a = new Thread(()->{
                //线程发送消息
                handlerB.sendMessage(new Message());
            });
            a.start();

源码里已有注释,不再缀叙。
你可能会说,每次都这么麻烦吗?实际上Android系统开发者已经考虑了这情况,封装了HandlerThread类来实现子线程的消息循环,实现方式和上述类似。重点是将Handler和Looper关联起来。

总结

以上从源码和应用角度剖析了Handler、Message、Looper原理及其三者之间关系,三者简称消息循环。消息循环在Android开发中应用广泛,我们不仅要能够运用自如,也可以从源码中学习闪光的设计点。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值