理解使用Handler

Handler是什么?

handler允许你发送和处理与线程的MessageQueue关联的消息和runnable(可运行对象)。每个handler实例都与一个线程和该线程的消息队列关联。当创建一个新的handler时,它会绑定到一个looper(循环器)。handler将消息和runnable传递到该循环器的消息队列,并在该循环器的线程上执行它们。

能把消息发送给MessageQueue,并负责处理Looper分给它的消息。

为什么要使用handler?

handler使用主要有两个原因:

  1. 安排延时定时消息和线程在未来某个时间执行。

  2. 将要在不同于自己的线程上执行的操作排队。

如何理解 Message、 MessageQueue、 Looper?

Message:消息对象,类似于链表的一个结点。

  • 2个整型数值:轻量级存储int类型的数据。

  • 1个Object:任意对象。

  • replyTo:线程通信时使用。

  • what:用户自定义的消息码,让接收者识别消息。

MessageQueue:消息队列,用于存放消息对象的数据结构。

  • 采用先进先出的方式管理Message。(这里的先依据的不是谁先入队,而是消息待发送的时间)

  • 每一个线程最多可以拥有一个。

Looper:消息队列的处理者(用于轮询消息队列的消息对象)

  • 每个线程只有一个Looper。

  • Looper.prepare():为当前线程创建Looper对象。

  • Looper.myLooper():可以获得当前线程的Looper对象。

Handler如何实现的消息发送与处理?

  • post形式将可运行对象(runnable)排进消息队列;(一般用于单个场景比如单一的倒计时弹框功能)

    post(Runnable),
    postAtTime(java.lang.Runnable, long),
    postDelayed(Runnable, Object, long)
    对于延时、定时消息,可以通过removeCallbacks(Runnable r)、removeCallbacks(Runnable r, Object token)、removeCallbacksAndMessages(Object token)方法进行取消。

  • sendMessage形式将包含处理程序的数据内容排进消息队列(需要实现处理程序hanldeMessage);(Message做为参数多用于判断条件的场景)

    sendEmptyMessage(int),
    sendMessage(Message),
    sendMessageAtTime(Message, long),
    sendMessageDelayed(Message, long)
    对于延时、定时消息,可以通过removeMessages(int what)、或removeMessages(int what, Object object)、removeCallbacksAndMessages(Object token)将指定消息移除。

无论是post还是sendMessage发送消息,最终都会调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper不断轮询调用MeaasgaQueue的next方法,如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着执行message的Runnable callback 或handleMessage方法。

最后,列举一些关于Handler的常见问题

1.子线程中能不能直接new一个Handler,为什么主线程可以?

不能,因为Handler 的构造方法中,会通过Looper.myLooper()获取looper对象,如果为空,则抛出异常,主线程则因为已在入口处ActivityThread的main方法中通过 Looper.prepareMainLooper()获取到这个对象,并通过 Looper.loop()开启循环,在子线程中若要使用handler,可先通过Loop.prepare获取到looper对象,并使用Looper.loop()开启循环

    public Handler(@Nullable Callback callback, boolean async) {
        //获取thread上的消息循环器
        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;
    }

2.Handler导致的内存泄露原因及其解决方案

原因:
1..Java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用
2.我们在Activity中使用非静态内部类初始化了一个Handler,此Handler就会持有当前Activity的引用。
3.我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们Activity页面关闭之后,存在引用关系:"未被处理 / 正处理的消息 -> Handler实例 -> 外部类",如果在Handler消息队列还有未处理的消息 / 正在处理消息时导致Activity不会被回收,从而造成内存泄漏

解决方案:
1.将Handler的子类设置成静态内部类,使用WeakReference弱引用持有Activity实例
2.当外部类结束生命周期时,清空Handler内消息队列

3.Handler postDealy后消息队列有什么变化?进队延时更短的消息,消息队列怎样处理 队内消息?

postDelayed传入的时间,会和当前的时间SystemClock.uptimeMillis()做加和,而不是单纯的只是用延时时间。延时消息会和当前消息队列里的消息头的执行时间做对比,如果比头的时间靠前,则会做为新的消息头,不然则会从消息头开始向后遍历,找到合适的位置插入延时消息。 postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper; Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message进队;

4.Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

MessageQueue#next 在没有消息的时候会阻塞,如何恢复?他不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态,所以他不会阻塞他。

5.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象?

一个线程可以有多个Handler,只有一个Looper对象,只有一个MessageQueue对象。Looper.prepare()函数中知道,。在Looper的prepare方法中创建了Looper对象,并放入到ThreadLocal中,并通过ThreadLocal来获取looper的对象, ThreadLocal的内部维护了一个ThreadLocalMap类,ThreadLocalMap是以当前thread做为key的,因此可以得知,一个线程最多只能有一个Looper对象,在Looper的构造方法中创建了MessageQueue对象,并赋值给mQueue字段。因为Looper对象只有一个,那么Messagequeue对象肯定只有一个。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值