Handler消息分发机制

Handler消息分发机制

1.      问题:

子线程如何更新UI

顺便解决在子线程中为啥handler要放在Looper中???

 

2.      资源

https://blog.csdn.net/bboyfeiyu/article/details/38555547

https://blog.csdn.net/guolin_blog/article/details/9991569

https://www.jb51.net/article/93199.htm(高深)

 

3.      分析

为啥我们要用Handler,因为Android  UI线程不安全。为啥不安全,因为AndroidUI线程操作并没有加锁,也就是说可能在非UI线程中刷新界面的时候,UI线程(或者其他非UI线程)也在刷新界面.这样就导致多个界面刷新的操作不能同步,导致线程不安全。如果,我们直接更新UI,但你不能做过多的耗时操作,否则你会使UI卡顿。

 

4.      流程

我们从UI线程(不用加Looper)开始分析消息分发机制

在Activity中 new Handler,然后我们去看看Handler的构造函数

最终它会有两个构造函数,我们只分析下面这个,另一个很简单就不提了,请看下图

第一个if只是用发射获取一些当前类的信息,并打印,不重要。

看mLooper=Looper.myLooper();

这是啥???

我们可以知道myLooper()返回给我们了一个Looper对象。

 

补充知识:

 

Handler是管理某个线程(也可能是进程)的循环消息队列,那它是不是要持有Looper和MessageQueue的对象。而我们看到handler中并没有设置Looper和MessageQueue成员变量。

那他们是怎么联系的???

通过Thread持有Looper、Looper持有MessageQueue,然后handler调用Thread.currentThread()获得当前线程Thread。

所以我们可以说Thread为handler绑定了LooperMessageQueue

 

Ok,现在我们应该理解到hander运行在Thread中,而Thread 可以帮我们获取到Looper与MessageQueue,至于Looper与MessageQueue是多久创建的我们后面说。我们已经创建好了一个handler,然后就是我们用的时候,调用handler方法sendMessage()

最后跟踪到

到了这里,就是把你发送的消息与当前的handler对象封装在Message中,和当前时间加上延迟更新时间一起传入enqueueMessage(),于是我们就通过这个方法把多线程消息有顺序的存储好了,比较简单不进去细看了。要读这个方法请注意Message是一个单链表,我们所有的数据都存在这个缓存单链表中,还要知道MessageQueue中的mMessages成员变量记录的是单链表的头。而且在这个方法中其用到了synchronized锁,保证了多线程时消息存放时的安全性。

 

好了,接下来我们就是依次的去取数据。如果我们在主线程(UI线程)中,自己写的代码层只要把数据发给handler,此时你的UI就更新了。但是,我们前面只解释了将多线程有序的缓存起来,怎么UI就更新好了。。。我们接下来说,UI handler在背后默默为我们做的事。。。

 

我们再补充点知识:


补充1:


UI线程(主线程)在程序启动时就开始创建了

在Android启动接口中的ActivityThread.main()方法中(不知道请百度)

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}

if (false) {
    Looper.myLooper().setMessageLogging(new
            LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

我们可以看到其实在UI 线程中,系统调用Looper.prepareMainLooper()

我们可以看到调了prepare();

sThreadLocal是啥???在Thread类中有一个成员变量

         ThreadLocal.ThreadLocalMap threadLocals 

其内部维护了一个数组 Entry[] table ,而其内部类Entry 是一个Key-Value,类似于Map, 如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

所以每个线程都有一个自己的 "Map" 来存储一些东西,其中的Key  的类型为ThreadLocal ,value 为任意类型。 而在Looper中定义了一个唯一的Key:

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

所以在上面展示的Loop.prepare()代码中的sThreadLocal.get的功能为向当前线程的Map中,取出Key为sThreadLocal对应的Value(Looper). 如下图:

t.threadLocals,就是上面提到的"Map"

所以sThreadLocal.get()查看UI线程是否有Looper对象,由于此时我们在安卓应用程序刚启动的入口,所以没有数据,于是,就创建了一个new Looper(),其构造函数如下:

好了 这样就解释了前面的留下来的问题,在Looper的构造函数中,我们为UIhandler  创建一个MessageQueue 并记录了当前线程Thread,


回到 ActivityThread.main()截图,接着系统为activity创建主线程线程ActivityThread,获得activity的与UI交互的handler等,注意最后一句Looper.Loop()是不是很熟悉。。这是用来开启无限循环取前面handler存储在Message中的数据的方法。

next()函数----- 从链表中取出消息

 Message next() {

        for (;;) {
            
            
            // 如果下一个Message执行时间还没到,就会被该函数阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {

                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                
                //  如果msg.target == null,表示为同步屏障,用于同步异步消息。
                //  当向链表插入(发送)一个同步屏障后,紧接着会向链表插入(发送)一个异步操作,一个Message.isAsynchronous=true 的消息到MessageQueue中。
                if (msg != null && msg.target == null) {
                    
                    //  查找同步屏障后的第一个异步的消息结果
                    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;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 链表中没有消息
                    nextPollTimeoutMillis = -1;
                }

                // 是否放弃链表中的所用消息
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 判断当前线程是否空闲,如果空闲,执行IdleHandler中的不紧急事件。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 如果没有需要执行的事件,阻塞线程
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                
                // 构建数组,用于存放空闲时,需要做的事情
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 空闲时,遍历处理一些非紧急事情
            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 {
                    //  钩子函数,执行非紧急事情,返回true,表示成功。
                    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;
        }
    }

 

取消息时,可能会做以下事情:

       1.  如果获取的消息是同步屏障,则向后遍历,找出链表中离同步屏障最近的一个异步操作,并执行。(注意:同步屏障没有被删除,一般在异步操作执行的内部调用函数将屏障删除。)

       2. 如果链表中没有消息或者消息的执行时间还没到,表示空闲。就可以执行MessageQueue.mIdleHandler中的非紧要的消息。

       3. 如果链表中和MessageQueue.mIdleHandler都没有要执行的消息。则调用nativePollOnce(),阻塞线程,阻塞时间为nextPollTimeoutMillis

在取出消息后,会执行Looper.loop()的下面代码,将消息分发出去

最终发送给了我们重写的handleMessage中。

好了,现在我们将在UI线程中用handler分发消息走了一遍,先小结下:

        客户端 :handler 向MessageQueue中发送和接受消息

        服务端: Looper  从MessageQueue中不停的取数据并处理(死循环),保证多线程有序

        系统:   ThreadLocalMap 为每个线程记录自己的服务Looper


解决问题:

在子线程中为啥handler要放在Looper中?

由于handler本身只起发送和接收消息的作用。不具有死循环有序的存储和取出多线程消息的功能,而Looper为其提供了这些功能,即没有处理多线程发生消息的能力。

 

如何在子线程更新UI?????

这就简单了,我们只要把让handler(在其他线程中创建)向主线程的MessageQueue发送更新UI的消息。

有几种方法:https://blog.csdn.net/xx_dd/article/details/52888319

下面给出一个用例

在new Handler()的时候我给其传入了一个Looper(context.getMainLooper)为其绑定。

 

总结:

1.      上面红色字体

2.      从功能上看handler的消息处理。handler的作用为发送与接受消息,MessageQueue为存取数据和锁线程的。Message消息缓存,是一个单链表。Looper起到死循环取数据和为handler提供MessageQueueThread对象。ThreadLocal存储Looper对象,在handler绑定MessageQueue起到了关键作用。

3.      消息到达MessageQueue,会按执行时间顺序排放在链表中。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值