锤爆Handler!!!

0.Handler的实现原理
从四个方面看Handler、Message、MessageQueue 和 Looper

Handler:负责消息的发送和处理
Message:消息对象,类似于链表的一个结点;
MessageQueue:消息队列,用于存放消息对象的数据结构;
Looper:消息队列的处理者(用于轮询消息队列的消息对象)
Handler发送消息时调用MessageQueue的enqueueMessage插入一条信息到MessageQueue,Looper 不断轮询调用MeaasgaQueue的next方法 如果发现message就调用handler的dispatchMessage, dispatchMessage被成功调用,接着调用handlerMessage()。

1.用一句话概括Handler,并简述其原理
Handler是Android系统的根本,在Android应用被启动的时候,会分配一个单独的虚拟机,虚拟机会执行ActivityThread中的main方法,在main方法中对主线程Looper进行了初始化,也就是几乎所有代码都执行在Handler内部。Handler也可以作为主线程和子线程通讯的桥梁。
Handler通过sendMessage发送消息,将消息放入MessageQueue中,在MessageQueue中通过时间的维度来进行排序,Looper通过调用loop方法不断的从MessageQueue中获取消息,执行Handler的dispatchMessage,最后调用handleMessage方法。
2.Handler的消息机制及其原理?
首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对0象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法,最终得到处理。这就是Handler机制整个的工作流程。
3.Handler中looper死循环为什么不导致应用卡死?
loop无限循环用于取出消息并将消息分发出去,没有消息时会阻塞在queue.next()里的nativePollOnce()方法里,并释放CPU资源进入休眠。Android的绝大部分操作都是通过Handler机制来完成的,如果没有消息,则不需要程序去响应,就不会有ANR。ANR一般是消息的处理过程中耗时太长导致没有及时响应用户操作。
4.主线程中Looper的轮询死循环为何没有阻塞主线程?
Looper轮询是死循环,但是当没有消息的时候他会block,ANR是当我们处理点击事件的时候5s内没有响应,我们在处理点击事件的时候也是用的Handler,所以一定会有消息执行,并且ANR也会发送Handler消息,所以不会阻塞主线程。
5.为什么系统不建议在子线程访问UI?(为什么不能在子线程更新UI?)
在某些情况下,在子线程中是可以更新UI的。但是在ViewRootImpl中对UI操作进行了checkThread,但是我们在OnCreate和onResume中可以使用子线程更新UI,由于我们在ActivityThread中的performResumeActivity方法中通过addView创建了ViewRootImpl,这个行为是在onResume之后调用的,所以在OnCreate和onResume可以进行更新UI。
但是我们不能在子线程中更新UI,因为如果添加了耗时操作之后,一旦ViewRootImpl被创建将会抛出异常。一旦在子线程中更新UI,容易产生并发问题。
6.一个Thread可以有几个Looper?几个Handler?
一个线程只能有一个Looper,可以有多个Handler,在线程中我们需要调用Looper.perpare,他会创建一个Looper并且将Looper保存在ThreadLocal中,每个线程都有一个LocalThreadMap,会将Looper保存在对应线程中的LocalThreadMap,key为ThreadLocal,value为Looper。
7.可以在子线程直接new一个Handler吗?那该怎么做?
可以在子线程中创建Handler,我们需要调用Looper.perpare和Looper.loop方法。或者通过获取主线程的looper来创建Handler。
java复制代码new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}).start();

(为什么主线程可以? )主线程则因为已在入口处ActivityThread的main方法中通过 Looper.prepareMainLooper()获取到这个对象,并通过 Looper.loop()开启循环
8.Message对象创建的方式有哪些 & 区别
1.Message msg = new Message(); 每次需要Message对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间
2.Message msg = Message.obtain(); obtainMessage能避免重复Message创建对象。它先判断消息池是不是为空,如果非空的话就从消息池 表头的Message取走,再把表头指向 next。 如果消息池为空的话说明还没有Message被放进去,那么就new出来一个Message对象。消息池使用 Message 链表结构实现,消息池默认最大值 50。消息在loop中被handler分发消费之后会执行回收的 操作,将该消息内部数据清空并添加到消息链表的表头。 3.Message msg = handler.obtainMessage(); 其内部也是调用的obtain()方法

Message.obtain来创建Message。这样会复用之前的Message的内存,不会频繁的创建对象,导致内存抖动。

9.使用Hanlder的postDealy()后消息队列会发生什么变化?
Handler发送消息到消息队列,消息队列是一个时间优先级队列,内部是一个单向链表。发动postDelay之后会将该消息进行时间排序存放到消息队列中
10.点击页面上的按钮后更新TextView的内容,谈谈你的理解?(阿里面试题)
点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障)
11.Handler是如何完成子线程和主线程通信的?
在主线程中创建Handler,在子线程中发送消息,放入到MessageQueue中,通过Looper.loop取出消息进行执行handleMessage,由于looper我们是在主线程初始化的,在初始化looper的时候会创建消息队列,所以消息是在主线程被执行的。
12.关于ThreadLocal,谈谈你的理解?
ThreadLocal类似于每个线程有一个单独的内存空间,不共享,ThreadLocal在set的时候会将数据存入对应线程的ThreadLocalMap中,key=ThreadLocal,value=值
13.谈谈消息机制Handler作用 ?有哪些要素 ?流程是怎样的 ?
负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新UI,所以在子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。 具体分为四大要素

Message(消息):需要被传递的消 息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息。 MessageQueue(消息队列):负责消息的存储与管理,负责管理由 Handler发送过来的Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。 Handler(消息处理器):负责Message的发送及处理。主要向消息池发送各种消息事件(Handler.sendMessage())和处理相应消息事件(Handler.handleMessage()),按照先进先出执行,内部使用的是单链表的结构。 Looper(消息池):负责关联线程以及消息的分发,在该线程下从MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。

在主线程创建的时候会创建一个Looper,同时也会在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。

通过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next() ,取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。


14.一个线程能否创建多个Handler,Handler跟Looper之间的对应关系 ?
一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler 以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) :MessageQueue(1) : Handler(N)
15.Handler 引起的内存泄露原因以及最佳解决方案
泄露原因: Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致Activity 泄露。 解决方案: 将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。
16.Handler导致的内存泄露你是如何解决的?
①什么是内存泄漏?
Java使用有向图机制,通过GC(Garbage Collection,垃圾回收)自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
② 内存泄漏的危害
内存泄露的危害就是会使虚拟机占用内存过高,导致OOM(内存溢出),程序出错。 说到这儿必须说说Android app 内存的事儿,Android的虚拟机是基于寄存器的Dalvik,Google原生OS最大堆大小一般是16M,有的机器为24M。在各大厂商的定制下这个大小也会变化,小米2S为例,这个值应该是96M,因此我们所能利用的内存空间是有限的。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;多次之后,程序占用内存超过系统限制,崩溃。 对于程序员来说,当我们程序不当使内存泄漏一直堆积下去最终超过这个限度的时候就会出现OutOfMemory的错误。这个时候应用程序就会崩溃,由于Dalvik的存在,幸好不会影响其他的程序。
17.为什么系统不建议在子线程访问UI?
Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态 这时你可能会问为何系统不对UI控件的访问加上锁机制呢?因为

加锁机制会让UI访问逻辑变的复杂;
加锁机制会降低UI的访问效率,因为加锁会阻塞某些线程的执行

18、使用Handler的postDealy后消息队列会有什么变化?
如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大
19、Message可以如何创建?哪种效果更好,为什么?
可以通过三种方法创建:

直接生成实例Message m = new Message
通过Message msg = Message.obtain
通过Message msg = mHandler.obtainMessage()

后两者效果更好,因为Android默认的消息池中消息数量是10,而后两者是直接在消息池中取出一个Message实例,这样做就可以避免多生成Message实例。
20、Handler 有哪些发送消息的方法
sendMessage(Message msg) sendMessageDelayed(Message msg, long uptimeMillis) post(Runnable r) postDelayed(Runnable r, long uptimeMillis) sendMessageAtTime(Message msg,long when)
21、MessageQueue是什么数据结构
内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。这点和传统的队列有点不一样,主要区别在于Android的这个队列中的消息是按照时间先后顺序来存储的,时间较早的消息,越靠近队头。 当然,我们也可以理解成,它是先进先出的,只是这里的先依据的不是谁先入队,而是消息待发送的时间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值