Android开发艺术探索相关知识点补充--第十章Android消息机制补充

1.Looper 死循环为什么不会导致应用卡死

线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在looper的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生nativeWake(),通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

2.消息阻塞、唤醒流程

获取消息则执行,没有消息则阻塞:

Looper.loop() -> MessageQueue.next() -> nativePollOnce(),如果有返回则执行消息,没有返回则阻塞在哪里等待唤醒

描述loop的阻塞流程:当与消息机制相关的几个对象初始化完毕后,Looper就开始进行消息循环,而loop()其实也就是循环的执行MessageQueue的next()方法,里面的native方法nativePollOnce()方法返回后,就代表next方法就可以从mMessages中获取一个消息,也就是说如果消息队列中没有消息存在nativePollOnce就不会返回。

handler根据获取到的looper进行消息插入,也就是对looper进行唤醒:

handler.post() -> MessageQueue.enqueueMessage.nativeWake()

hander唤醒looper流程:handler发送消息handler.post一个message,最终调用MessageQueue的enqueueMessage方法,里面有一个native方法叫nativeWake(),此时handler就往消息队列里面添加了一条消息,并且通过底层唤醒了阻塞状态的线程循环,开始处理消息;

nativePollOnce():用于提取消息队列中的消息

nativeWake():用于唤醒功能,在添加消息到消息队列enqueueMessage(), 或者把消息从消息队列中全部移除quit(),再有需要时都会调用nativeWake方法。

上面这块看看源码就可以很清楚,相关的文章:https://blog.csdn.net/ksjay_1943/article/details/54894589

https://blog.csdn.net/andywuchuanlong/article/details/48179165

3.Handler 是如何能够线程切换

Handler创建的时候会采用当前线程的Looper来构造消息循环系统Looper在哪个线程创建,就跟哪个线程绑定**,并且Handler是在他关联的Looper对应的线程中处理消息的。

那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

4.子线程有哪些更新UI的方法

这个问题的核心点就在于:需要使用主线程的looper去做这个事情,那怎么使用主线程的looper呢?

Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。

方式一:在主线程中new Handler()然后在子线程中使用post()出来的消息就是使用主线程的looper,就可以进行UI更新。

方式二:用Activity对象的runOnUiThread方法

方式三:在子线程中创建Handler,传入getMainLooper,那么该handler发出去的消息其实就是使用的主线程的looper进行处理

方式四:View.post(Runnable r)

方式五:使用AsyncTask

其实以上所有方式的本质就是切换到主线程的handler(是和主线程的looper绑定的),进行发送消息,在主线程中更新UI

5.handler是如何能够线程切换

Handler创建的时候会采用当前线程的Looper来构造消息循环系统Looper在哪个线程创建,就跟哪个线程绑定**,并且Handler是在他关联的Looper对应的线程中处理消息的。

那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

较多来自:https://maimai.cn/article/detail?fid=1297573771&efid=nxRR2KPlXlG79J8y8V2ImQ

6.线程、handler、message的对应关系

  • 一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler
  • 以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)

7.Handler 引起的内存泄露原因以及最佳解决方案

泄露原因:

  • Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决方案:

  • 将 Handler 定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。

8.使用Handler的postDealy后消息队列会有什么变化

如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper阻塞,到时间就唤醒它。但如果此时要加入新消息,该消息队列的对头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大

9.主线程looper没有消息时的阻塞和ANR的关系

  • 阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
  • 造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。

10.可以在子线程直接new一个Handler吗?怎么做?

不可以,因为在主线程中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护Looper对象,所以需要我们自己手动维护。所以要在子线程开启Handler要先创建Looper,并开启Looper循环

11.Message可以如何创建?哪种效果更好,为什么?

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

后两者效果更好,因为Android默认的消息池中消息数量是10,而后两者是直接在消息池中取出一个Message实例,这样做就可以避免多生成Message实例。

参考:https://maimai.cn/article/detail?fid=1281863405&efid=HE6XUxqlBux8-3VhhlISIw

你可能感兴趣的:(Android面试复习整理)