博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
大话Android中的Handler机制
阅读量:4354 次
发布时间:2019-06-07

本文共 5006 字,大约阅读时间需要 16 分钟。

在Android的线程间通信中,Handler独当一面,无论是framework层还是app层中都出现的相当频繁,有必要好好的拿出来深挖一下它的实现原理。而要说Handler的通信机制,除了Handler外,另外几个重要的类是不得不提的,他们就是Looper、MessageQueue、Message。在仔细研读这几个类的代码后,我试着用一件身边的事情来阐述他们之间的关系。

好的,下面开始我们的角色扮演游戏。Handler饰演寄信的小白,Looper饰演店员B,MessageQueue饰演店员A,Message饰演信。

小白同学逛某宝的时候看到一个店铺,店铺slogen大概是这样——“写封信给N年后的自己”,小白看到这个创意被深深吸引,果断下单,的标题是写给10年后的自己,内容非常简洁,大致是这样的:当你看到这封信的时候说明咱家还没拆迁,别死等了,好好工作吧(请忽略信内容 -_-)。店铺接单后,店员A不久便制作出了小白的信件然后按照小白要求的10年延迟时间将信件按延迟时间从小到大插入到店里存信件的书架中,同时店员A还需要负责从书架取信(你丫放的信只有你知道怎么取)。而店里还有店员B,他每天的工作内容是问店员A拿第一封信,如果时间已到就把它拿走寄出去。至此,故事剧情结束。

 


 

 

我们把上面的剧情再按程序的逻辑走一遍,看是否有瑕疵。Handler是Message的发起者,它把Message告诉MessageQueue,MessageQueue按执行时间when的大小,将Message插入单向链表中(下单),Looper不断从MessageQueue里取出Message检测执行时间when并执行(取信、寄出)。消息的入口和出口都拎出来了,但是还是有瑕疵:

问题1、Handler是如何与Looper、MessageQueue建立关联的?

当创建Handler对象会传入一个Looper对象或者通过静态方法Looper.myLooper()拿到当前线程的Looper对象及Looper对象中的MessageQueue对象。

问题2、既然是多线程通信,那当Handler在sendMessage的时候,如何知道自己的出生地是哪个线程,换句话说将Message给到哪个线程的MessageQueue?

在问题1中我们已经明确了Handler大部分情况下是通过Looper.myLooper()获得当前线程的Looper对象,拿到了Looper对象自然也就拿到了对应的MessageQueue对象,但是现在问题来了,Looper.myLooper()作为一个静态方法是如何做到返回调用该方法的线程的Looper对象的呢?嘿嘿,下面ThreadLocal<T>隆重登场!Looper类中有一神奇的静态常量ThreadLocal<Looper> sThreadLocal,它能对访问做到线程隔离,即在线程A中get()只能拿到在线程A中set进去的值(关于ThreadLocal<T>这里只讲这么多)。正是基于ThreadLocal这种神奇的实现,使得我们可以通过Looper.myLooper()获得当前线程的Looper对象。贴代码

public final class Looper {    ...    public static void prepare() {        prepare(true);    }    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));    }    ...      /**     * 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();    }    ...}

可以看到,myLooper()返回不为空的前置条件是Looper.prepare()被调用,所以如果要在子线程new一个Handler,切记先在子线程中调用prepare()并且一个线程只能调用一次哦!什么?你问为什么在主线程中不需要调prepare()?主线程当然也要调,只不过主线程在进程启动时系统就帮我们把Looper prepare好了(对启动过程感兴趣的可以去看看android.app.ActivityThread::main(String[])),千万不要再次调用了。

问题3、多个线程都在sendMessage,MessageQueue在插入消息时如何做到线程同步?

这个不太好吹,就是加了同步锁锁住了当前MessageQueue对象呗,详见下方代码

public final class MessageQueue {    ....    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 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);            }        }        return true;    }    ....}

问题4、Looper对象以死循环的方式取Message,会不会导致线程阻塞,主线程这样操作不会ANR吗?当队列中没有Message时,是否仍会造成CPU资源消耗?

既然死循环,那当然会阻塞,而且要的就是阻塞,是不是很惊喜~原因在于main函数作为入口,main函数执行完毕进程就结束了,那为了保持我们的app持续的运行,当然要想办法不让main执行完毕,而loop()方法正好达到了这个目的。至于是否ANR,我们要知道Android的所有生命周期都是通过Handler机制处理的,如果Handler中的某个处理消息的方法耗时过长,就会导致其它消息响应不及时,如果这些消息里有用户的操作类消息或者别的ANR敏感的消息,就可能出现ANR。
关于死循环是否会造成无意义的资源消耗,这个是不会的,这里用到了linux的管道和epoll模型,在取消息的时候通过nativePollOnce(ptr, nextPollTimeoutMillis);阻塞循环,等待nextPollTimeoutMillis后继续取消息(有Message但是when未到达)或阻塞直到管道中有新消息写入(nextPollTimeoutMillis=-1),而在入队的时候检测并唤醒epoll。


 

了解了Handler机制后,在使用Handler的时候要注意以下问题:

1、初始化Handler前务必调用Looper.prepare(); 主线程中不要画蛇添足去操作Looper;
2、sendMessage前确保Looper.loop()已被调用;
3、利用Message.obtain()的复用机制,避免创建对象的资源消耗。

转载于:https://www.cnblogs.com/duanzi6/p/9035371.html

你可能感兴趣的文章
关于beescms如何去掉底部版权信息
查看>>
CodeForces - 13D Triangles
查看>>
面试题
查看>>
ELK简单使用
查看>>
Mysql 新建用户以及授权远程连接操作
查看>>
【Hive学习之五】Hive 参数&动态分区&分桶
查看>>
##DAY11 UITableView编辑
查看>>
为什么程序员都是夜猫子
查看>>
linux shell program summary
查看>>
流畅的python和cookbook学习笔记(二)
查看>>
【转】如何用WINDBG分析64位机上32位程序的DUMP文件
查看>>
C语言基础二维数组
查看>>
select函数
查看>>
Android中的调试
查看>>
C++ 用RGB 三种颜色绘图
查看>>
memcache和memcached的区别
查看>>
[置顶] 处世悬镜之舍之
查看>>
Android 编译时出现r cannot be resolved to a variable
查看>>
清理系统方法
查看>>
zoj 2860 四边形优化dp
查看>>