目录
为什么子线程的消息可以放到主线程中处理?即内存共享实现跨线程通信。
Handler为什么会引发内存泄漏?为什么其他的内部类不会引发内存泄漏?
为何主线程可以new handler?如果要在子线程中new handler需要做哪些准备?
子线程中维护的Looper,消息队列无消息的时候 的处理方案是什么?有什么用?
Looper函数中quit()和quitSafely()的区别?
既然可以存在多个 Handler 往 MessageQueue 中 添加数据(发消息时各个 Handler 可能处于不同线 程),那它内部是如何确保线程安全的?
一.Handler是什么
Android中的消息通信机制,用于 子线程与主线程间的通讯,实现了一种 非堵塞的消息传递机制,把子线程中的 UI更新信息 发送给主线程(UI线程),以此完成UI更新操作。Android中主线程不能进行耗时操作,所有的耗时操作都需要在子线程中进行,耗时操作完成后如果需要更新ui,就需要把信息传给主线程更新ui。
Handler消息机制用于同进程的线程间通信
可以说只要有异步线程与主线程通信的地方就一定会有 Handler。
在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理。
使用Handler消息传递机制主要是为了多个线程并发更新UI的同时,保证线程安全
二 handler处理消息机制流程分析
子线程 发送MSG
handler.sendMessage() --> handler.enqueueMessage() --> MessageQueue.enqueueMessage()
主线程 取消息 处理消息
Activity.main()-->Looper.loop() --> MessageQueue.next() --> handler.dispatchMessage --> handler.handleMessage()
通过Looper.loop() 开启一个轮询,不断遍历消息队列,分发消息后处理消息
在ActivityThread.java的main函数中有 准备Looper的函数 和开启轮询的函数
三 面试题
一个线程中能有几个Handler?
一个线程中能有多个Handler,但只能有一个Looper机制
为什么子线程的消息可以放到主线程中处理?即内存共享实现跨线程通信。
线程间内存是共享的,MessageQueue也是共享的,因此主线程也能访问MessageQueue
Handler为什么会引发内存泄漏?为什么其他的内部类不会引发内存泄漏?
持有链 sThreadLocal 是静态对象 因此是gcroot
static sThreadLocal --> Looper --> MessageQueue --> Message -->Handler --> Activity.this
内存泄漏:比如说 Handler发送了一个延时消息,在消息发送前Activity已经destory了,但此时由于Message不为空,不会被垃圾回收,因此就会引发内存泄漏。
解决方法:
将handler置为静态对象,因此handler是不具备持有外部类的功能的,给handler添加一个弱引用或者软引用即可。
为何主线程可以new handler?如果要在子线程中new handler需要做哪些准备?
handler在new的过程中会去拿到sThreadLocal 的looper
因此在子线程中new handler需要在looper.perpare()后,在looper.loop()前
如何在子线程中正确的使用Handler?
使用HandlerThread来创建Handler 其中有一个Looper成员,同时运用了锁机制,在子线程run()中创建完looper后并 主线程调用handler的getLooper()方法才会执行,不然的话getLooper()方法会一直wait()
子线程中维护的Looper,消息队列无消息的时候 的处理方案是什么?有什么用?
在子线程中,执行Looper.perpare() Looper.loop()之后 由于消息队列没有消息了之后,子线程会一直卡死在Looper的轮询中的这个地方
for (;;) { Message msg = queue.next(); // might block ... }
然后该子线程就一直在等待,也干不了其他事情了也就是被卡死了。
因此
如果在子线程中创建了一个Handler,那么就必须做三个操作:
prepare();
loop();
quit();
然后让我们来分析一下quit()函数
在Handler机制里面有的Looper有两个函数,叫做quitSafely()和quit()函数,这两个函数是调用的MessageQueue的quit()。
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); }
再进入到MessageQueue的quit()函数。
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
它会remove消息,把消息队列中的全部消息给干掉。
把消息全部干掉,也就释放了内存。
private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { if (p.when > now) { removeAllMessagesLocked(); } else { Message n; for (;;) { n = p.next; if (n == null) { return; } if (n.when > now) { break; } p = n; } p.next = null; do { p = n; n = p.next; p.recycleUnchecked(); } while (n != null); } } }
在quit()函数的最后一行,有一个nativeWake()的函数 往下执行就会唤醒之前被阻塞的nativePollOnce(),使得方法next()方法中的for循环继续执行
//native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行 //阻塞操作,等待nextPollTimeoutMillis时长 nativePollOnce(ptr, nextPollTimeoutMillis);
接下来发现Message为null后就会结束循环,Looper结束,即可释放线程
主线程中能否调用quit()方法?
是不能的。它会抛出一个异常,让程序挂掉。
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } ...... }
Looper函数中quit()和quitSafely()的区别?
Looper.quit()
调用后直接终止
Looper
,不在处理任何Message
,所有尝试把Message
放进消息队列的操作都会失败,比如Handler.sendMessage()
会返回 false,但是存在不安全性,因为有可能有Message
还在消息队列中没来的及处理就终止Looper
了。
Looper.quitsafely()
调用后会在所有消息都处理后再终止
Looper
,所有尝试把Message
放进消息队列的操作也都会失败。
既然可以存在多个 Handler 往 MessageQueue 中 添加数据(发消息时各个 Handler 可能处于不同线 程),那它内部是如何确保线程安全的?
可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程) 添加消息的方法enqueueMessage()中有synchronize修饰,取消息的方法next()中也有synchronize修饰。 Handler的delay消息(延迟消息)时间准确吗? 由于上述的加锁操作,所以时间不能保证完全准确。
我们使用 Message时应该如何创建它?
使用Message的obtain()方法创建,直接new出来容易造成内存抖动。 内存抖动是由于频繁new对象,gc频繁回收导致,而且由于可能被别的地方持有导致无法及时回收所以会导致内存占用越来越高。 使用obtain()对内存复用,可以避免内存抖动的发生。其内部维护了一个Message池,其是一个链表结构,当调用obtain()的时候会复用表头的Message,然后会指向下一个。如果表头没有可复用的message则会创建一个新的对象,这个对象池的最大长度是50。
一直取new 而不用obtain() 会产生很多内存碎片
Looper死循环为什么不会导致应用卡死?
卡死就是发生了ANR
事实上我们所有的Activity和Service都是运行在loop()函数中,以消息的方式存在,所以在没有消息产生的时候,looper会被block(阻塞),主线程会进入休眠,一旦有输入事件或者Looper添加消息的操作后主线程就会被唤醒,从而对事件进行响应,所以不会导致ANR 简单来说looper的阻塞表明没有事件输入,而ANR是由于有事件没响应导致,所以looper的死循环并不会导致应用卡死。
Android线程间通信的四种实现方式?
1,通过Handler机制.
主线程中定义Handler,子线程发消息,通知Handler完成UI更新,Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递content对象或通过接口调用。 另外Handler机制与Activity生命周期不一致的原因,容易导致内存泄漏,不推荐使用。
2,runOnUiThread方法
用Activity对象的runOnUiThread方法更新,在子线程中通过runOnUiThread()方法更新UI,强烈推荐使用。
3,View.post(Runnable r)
这种方法更简单,但需要传递要更新的View过去,推荐使用