https://blog.csdn.net/white_156/article/details/108301979?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161408346616780357296218%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=161408346616780357296218&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-108301979.first_rank_v2_pc_rank_v29_10&utm_term=handler
mHandler.sendMessage(message);
–》Handler.java
(sendMessage–>sendMessageDelayed–>sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);–》enqueueMessage(queue, msg, uptimeMillis);–》queue.enqueueMessage(msg, uptimeMillis【msg.target = this;】)😉
–》MessageQueue.java
(enqueueMessage放消息)
SystemClock.uptimeMillis():开机到现在的毫秒数
System.currentTimeMillis():1970.1.1到现在的毫秒数(可以被System.setCurrentTimeMillis修改,不准确)
mAsynchronous:默认false,如果设置为true:将会异步处理此消息,第12个问题知道如何实现了吗?
这几个参数在注释中有详细的说明,这里需要说说mCallback,
1关键字
1)Looper:
一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
2)Handler:
你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):
用来存放线程放入的消息。
通过msg.target保证MissageQueue中的每个msg交由发送message的handler进行处理,
4)线程:
UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
1.Handler创建消息
每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。
2.Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。
3.Handler处理消息
UI主线程通过Looper循环查询消息队列UI_MessageQueue,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。
子线程通过Handler、Looper与UI主线程通信的流程如图所示。
主线程发送消息给子线程, 在主线程中实例化一个Handler,然后让他与子线程相关联(只要它与子线程的Looper相关联即可),这样子它处理的消息就是该子线程中的消息队列,而处理的逻辑都是在该子线程中执行的,不会占用主线程的时间。
当主线程中有耗时的操作时,需要在子线程中完成,通常我们就把这个逻辑放在HandlerThread的对象中执行(该对象就是一个子线程),然后在需要开始执行逻辑的地方发送一个Message来通知一下就可以了。
sendtoTarget,是message的方法(这个要事先知道目标是谁,才能调)
sendMessage是Handler的方法(这个是目标直接自己调)
小结:
Handler与哪个线程的Looper相关联,那么它的消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。(最常见的就是Handler与主线程关联,那么接收Looper回传的消息后的逻辑就会在主线程中执行)
当主线程中需要与子线程进行通信时(比如将耗时操作放在子线程中),建议使用HandlerThread。同时要注意,千万不要去重写它的run方法。
https://www.cnblogs.com/dendai-05/p/6945159.html
注意事项
假设如果在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,马么会产生生么样的问题?——更新界面混乱;
如果对更新UI 的操作都加锁处理的话会产生什么样子的问题?——性能下降
不用关心多线程的问题,更新UI的操作,都是在主线程的消息队列当中轮询处理的。
自己做的: driveRenameWorker和driveFavoriteWork 的task.run 改变为 taskmanager.executeTask(***Task),
run方法是直接返回最终结果,executeTask方法是实时的反馈进程的状态结果,方便Fragment对不同的状态进行不同的UI处理
面试问题:
Handler:描述handler工作机制
3.1. 为什么Looper和MessageQueue只有一个
Looper有两个关键的放perpare(),loop()方法,在prepare方法会创建一个Looper对象,然后保存在ThreadLocal中,该方法只能被调用一次,否则会抛出异常,这样保证了一个线程只有一个looper,Looper构造器中创建MessageQueue对象。所以只有一个looper和MessageQueue
3.2. 什么是ThreadLocal,源码怎么保证上述两个变量只有一个。
ThreadLocal:Thread–>ThreadLocal–>ThreadlocalMap(key , value)
Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(true));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
3.3 Looper一直在循环MessageQueue,没有消息时为什么不会造成主线程ANR?
为什么Looper.loop不会卡死UI
loop采用的是epoll+pipe(模型),有消息就依次执行,没消息就block住,让出CPU,等有消息了,epoll会往pipe中写一个字符,把主线程从block状态唤起,主线程就继续依次执行消息。
epoll模型
当没有消息的时候会epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。
所有的ui操作都通过handler来发消息操作。
比如屏幕刷新16ms一个消息,你的各种点击事件,所以就会有句柄写操作,唤醒上文的wait操作,所以不会被卡死了。
3.4 子线程中如何创建handler,为什么要加Looper.prepare。为什么主线程不用?
主线程的在ActivityThread中的main方法中已经Looper.prepareMainLooper和.loop了
3.5 造成内存泄漏的原因?解决。
静态:如果是普通内部类,那么会保持对他所在的外部类的引用,为了避免泄露这个外部类,应该设置为static嵌套类,
弱引用,GC必然回收
static主要是没有了外部的引用,不能 (.this)了。
handler 的 post 和send
post发送runnable,但最终也是发送message
send发送message
handler的延迟机制
delay,从开机到现在的毫秒数+delay延迟作为消息的when属性
if (now < msg.when) {
// 消息执行时间未到,这里设置一个唤醒时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
handler的系统时间
直接显示不随着时间动:
String time = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
然后通过handler发送
随时间变化的
通过一个TimerTask
//从0秒开始,每隔1000ms启动一次Timertask
timer.schedule(timerTask, 0, 1000);
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
Message message = new Message();
String time = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
message.obj = time ;
handler.sendMessage(message);
}
};
HandlerThread
本质:Handler + Thread + Looper,是一个Thread内部有Looper
- 创建一个HandlerThread,即创建了一个包含Looper的线程。
HandlerThread handlerThread = new HandlerThread("leochin.com");
handlerThread.start(); //创建HandlerThread后一定要记得start()
- 获取HandlerThread的Looper
Looper looper = handlerThread.getLooper();
- 创建Handler,通过Looper初始化
Handler handler = new Handler(looper){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
}
通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。
如果想让HandlerThread退出,则需要调用handlerThread.quit();
。
- 优点是不会有堵塞,减少对性能的消耗,缺点是不能进行多任务的处理,需要等待进行处理,处理效率较低。
- 与线程池注重并发不同,HandlerThread是一个串行队列,HandlerThread背后只有一个线程。