1、谈谈消息机制Hander作用、有哪些要素、流程是怎样的?
作用:当子线程中进行耗时操作后需要更新UI时,通过Handler将更新UI的操作切换到主线程中执行。
-
四要素:
- Message: 消息对象,是线程间通讯的消息载体。
- MessageQueue:消息队列,用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
- Looper:轮播器,负责管理线程的消息队列和消息循环。
- Handler:在主线程中负责发送和处理消息。
-
工作流程:
- 当 Handler.sendMessage() 发送消息时,会通过调用 MessageQueue.enqueueMessage() 向消息队列MessageQueue加入一条消息。
- 然后通过轮播器 Looper.loop() 不断的在MessageQueue中取出消息,调用 Handler.dispatchMessage() 去将消息传递给目标 Handler;
- 目标 Handler 收到消息后调用 handlerMessage 进行消息真正的处理。
2、post(Runnable) 和sendMessage(msg)的区别
从Post方法的源码可以知道,其实质 post(Runnable) 这个方法只是在 sendMessage() 上进行了一层
封装
,通过 sendMessageDelayed 把 Runnable 对象添加到了 MessageQueue 中,而 sendMessage 也同样将 Message 加入到 MessageQueue,所以两者内部实现是一样的。
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
为什么系统不建议在子线程访问UI?
系统不建议在子线程访问UI的原因是,UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:
- 上锁会让UI控件变得复杂和低效
- 上锁后会阻塞某些进程的执行
一个线程可以有几个Looper?几个Handler?
- 一个Thread只能有一个Looper,可以有多个Handler。
- Looper有一个MessageQueue,可以处理来自多个Handler的Message;MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;Message中记录了负责发送和处理消息的Handler;Handler中有Looper和MessageQueue;
如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?
通过Looper.prepare()可将一个Thread线程转换成Looper线程。Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。
可以在子线程直接new一个Handler吗?那该怎么做?
可以,不同于主线程直接new一个Handler,由于子线程的Looper需要手动去创建,在创建Handler时需要多一些方法。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//为子线程创建Looper
new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//子线程消息处理
}
};
Looper.loop(); //开启消息轮询
}
}).start();
Message可以如何创建?哪种效果更好,为什么?
创建Message对象的几种方式:
- Message msg = new Message();
- Message msg = Message.obtain();
- Message msg = handler1.obtainMessage();
后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message
ThreadLocal有什么作用?
ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
- 底层数据结构:每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。
主线程中Looper的轮询死循环为何没有阻塞主线程?
因为 Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者 Activity 的生命周期都是运行在 Looper.loop() 的控制之下,一旦退出消息循环,应用也就停止了。只能说是对某个消息的处理阻塞了Looper.loop()。
使用Hanlder的postDealy()后消息队列会发生什么变化?
post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。
Looper中 quit() 和 quitSafely() 的区别
- 当 Looper的 quit() 或 quitSafely() 被调用时,就会调用 MessageQueue 的 quit() 方法,分别对应的是 removeAllMessagesLocked() 和 removeAllFutureMessagesLocked() 方法。
- 区别在于,quit() 会清空所有消息(无论延时或非延时),quitSafely() 只清空延时消息,最终 next 方法都是返回 null。
- 注意:无论调用哪个方法,当 Looper 退出后,Handler 发送的消息都会失败。这时候再通过 Handler 调用 sendMessage 或 post 等方法发送消息时均返回 false,表示消息没有成功放入消息队列 MessageQueue 中,因为消息队列已经退出了。
Handler 引起的内存泄露以及解决方法
所创建的 Handler 不是静态内部类,它会隐匿持有 Activity 的引用。当 Activity 要回收的话,而 Handler 内部有可能在做耗时操作,所以 Handler 所持有的 Activity 的引用不能被释放。最终导致 Activity 没有被回收,停留在队列内存当中,造成内存泄露。
总结:静态内部类持有外部类的匿名引用,导致外部Activty 无法释放。
解决方法:
- 将Handler 设置为静态内部类。
- 在 Activity 的生命周期 onDestroy() 方法中,调用 handler.removeCallbacks() 方法。
- handler 内部持有外部 activity 的弱引用。
Android中有哪些方便线程切换的类?
对Handler进一步的封装的几个类:
- AsyncTask:底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
- HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
- IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。
AsyncTask相比Handler有什么优点?不足呢?
- AsyncTask:创建异步任务更简单,过程可控。在使用多个异步操作并且需要进行 UI 变更时,变得复杂。
- Handler:结构清晰,功能明确。对于多个后台任务时,清晰简单。在单个后台任务时,显得代码过多,结构复杂。多任务同时执行时不易精确控制线程。
子线程有哪些更新UI的方法?
- 主线程中定义 Handler,子线程通过 handler 发送消息,主线程 Handler 的 handleMessage 更新UI。
- View.post(Runnable r) 。
- 用 Activity 对象的 runOnUiThread 方法。
使用AsyncTask需要注意什么?
- AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress 和 Result 这三个泛型参数,如果 AsyncTask 不需要传递具体的参数,那么三个参数可以用 Void 来代替。
- AsyncTask提供了 4 个核心方法,除了doInBackground在线程池中执行其余在主线程中执行。
AsyncTask中使用的线程池大小?
在AsyncTask内部实现有两个线程池:
- SerialExecutor:用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5、线程池大小为128,而3.0以后变为同一时间只能处理一个任务
- THREAD_POOL_EXECUTOR:用于真正执行任务。
HandlerThread有什么特点?
HandlerThread是一个线程类,它继承自Thread。与普通Thread不同,HandlerThread具有消息循环的效果,这是因为它内部HandlerThread.run()方法中有Looper,能通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。
在子线程中如何使用HandlerThread
- 实例化一个HandlerThread对象,参数是该线程的名称;
- 通过 HandlerThread.start()开启线程;
- 实例化一个Handler并传入HandlerThread中的looper对象,使得与HandlerThread绑定;
- 利用Handler即可执行异步任务;
- 当不需要HandlerThread时,通过HandlerThread.quit()/quitSafely()方法来终止线程的执行。
IntentService的特点?
不同于线程,IntentService是服务,优先级比线程高,更不容易被系统杀死,因此较适合执行一些高优先级的后台任务;不同于普通Service,IntentService可自动创建子线程来执行任务,且任务执行完毕后自动退出。
为何不用bindService方式创建IntentService?
IntentService的工作原理是,在IntentService的onCreate()里会创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage()方法会去调用IntentService的onHandleIntent(),这也是为什么可在该方法中处理后台任务的逻辑;当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。
线程池的好处、原理、类型?
- (1)线程池的好处:
- 重用线程池中的线程,避免线程的创建和销毁带来的性能消耗;
- 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象;
- 进行线程管理,提供定时/循环间隔执行等功能
- (2)线程池的分类:
- FixThreadPool:线程数量固定的线程池,所有线程都是核心线程,当线程空闲时不会被回收;能快速响应外界请求。
- CachedThreadPool:线程数量不定的线程池(最大线程数为Integer.MAX_VALUE),只有非核心线程,空闲线程有超时机制,超时回收;适合于执行大量的耗时较少的任务
- ScheduledThreadPool:核心线程数量固定,非核心线程数量不定;可进行定时任务和固定周期的任务。
- SingleThreadExecutor:只有一个核心线程,可确保所有的任务都在同一个线程中按顺序执行;好处是无需处理线程同步问题。
- (3)线程池的原理:实际上通过ThreadPoolExecutor并通过一系列参数来配置各种各样的线程池,具体的参数有:
- corePoolSize核心线程数:一般会在线程中一直存活
- maximumPoolSize最大线程数:当活动线程数达到这个数值后,后续的任务将会被阻塞
- keepAliveTime非核心线程超时时间:超过这个时长,闲置的非核心线程就会被回收
- unit:用于指定keepAliveTime参数的时间单位
- workQueue任务队列:通过线程池的execute()方法提交的Runnable对象会存储在这个参数中。
- threadFactory:线程工厂,可创建新线程
- handler:在线程池无法执行新任务时进行调度
ThreadPoolExecutor的工作策略?
- 若程池中的线程数量未达到核心线程数,则会直接启动一个核心线程执行任务。
- 若线程池中的线程数量已达到或者超过核心线程数量,则任务会被插入到任务列表等待执行。
- 若任务无法插入到任务列表中,往往由于任务列表已满,此时如果
- 线程数量未达到线程池最大线程数,则会启动一个非核心线程执行任务;
- 线程数量已达到线程池规定的最大值,则拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。