Handler老生常谈,总感觉会用,搞清楚了,但是呢,又总感觉缺了下什么,记录下。好记性不如烂键盘。
一、Handler源码吃透
首先,我们需要确定前提的是一个Thread线程只有一个Looper,一个MessageQueue,多个Handler对象。
Handler机制的整体架构类似于一个传送带装置。
- Handler:类似于给传送带放置货物(sendMessage()),从传送带上取货物(处理货物)(dispatchMessage())
- Message:传送带上的货物
- Looper:使传送带循环转起来的电机(Looper.loop())
- MessageQueue:传送带
1.1 Handler
在一个线程中可以有很多个Handler,它们都可以发送Message,发送的的话,有很多的sendMessage方法,最终是由MessageQueue.enqueueMessage处理。并在处理时,由他们各自的handleMessage()函数处理。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
1.2 Message
- Message是一个单向链表结构的实体类,保持了很多信息。
- 它有一个next变量指向下一个MessageZ节点,并保持着一个缓存池。当被释放时,调用它的recycle()函数。但是它并不会被释放,而是缓存起来,等待下一次调用obtain()方法时,重新赋值使用。
- 另外,它的target变量将会持有外部Handler,这也是内存泄漏的主要因素。
public void recycle() {
...
recycleUnchecked();
}
void recycleUnchecked() {
what = 0;
...
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
1.3 MessageQueue
它通过enqueueMessage()方法来将Message对象按时间when的先后加入链表中,通过next()方法从链表中取出Message。它始终有一个mMessage指向链表头结点。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
1.4 Looper
每个Thread线程只有一个Looper对象(它是有ThreadLocal来保证的。),而每个Looper对象也只有一个MessageQueue。
- Looper.prepareMainLooper()来创建主线程的Looper对象
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
- Looper.prepare()创建子线程中的Looper对象,先从ThreadLocal中获取,有就抛出异常,一个线程只能创建一个。没有就会实例化一个Looper对象,并set到ThreadLocal中。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
- Looper.loop()开启死循环来,不断遍历MessageQueue看是否有消息,有就通过msg.target(实际就是Handler)来dispatchMessage()处理消息,最后回收Message。
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next();
...
try {
msg.target.dispatchMessage(msg);
...
} catch (Exception exception) {
...
}
msg.recycleUnchecked();
}
}
二、Looper死循环为什么不会导致应用卡死?
基于前面我们已经知道了,App的启动流程,最终会Zygote进程孵化出App进程。而ActivityThread就是App的进程所在,在它的main()方法中,实例化了一个主线程的Looper对象,并将其存储在主线程的ThreadLocal.ThreadLocalMap中。
我们知道,如果main()方法执行完,那么意味着进程就结束了。但是这是App进程,我们想要它结束吗?Android并不想让App进程退出,所以这里Looper.loop()死循环阻塞也是这个作用,保持App进程。
而Android是以事件为驱动的系统,但没有事件来时,就应该去展示静态的界面。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
....
Looper.loop();
}
三、使用Handler的postDelay消息队列有什么变化?
-
- Hanlder的postDelayed()有三个重载方法,它们都会去调用sendMessageDelayed()方法。
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
-
- 在sendMessageDelayed()它将延迟时间与系统启动时间相加,作为一个消息需要处理的时间。通过这个时间值插入到MessageQueue中。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
四、如何保证多个Handler线程安全?
一个线程只有一个Looper和一个MessageQueue实例,可以有多个Handler对象。在MessageQueue中像enqueueMessage()、next()方法都加了synchronized(this)同步锁,保证线程安全。
五、Message如何创建?哪种方式更好?
-
- 使用new Message()方法创建
-
- 使用Message.obtain()方式创建
我们知道在系统中可能会发送无数个Message,使用第一种方式的话会导致频繁创建和频繁销毁,这将导致内存抖动。所以Handler中推进使用第二种方式来创建,它优先使用缓冲池中的Message,如果缓冲池没有可用的才回去实例化一个message。但Message对象被Handler处理完后,不是直接销毁,而是将其重置,并缓存到缓存池中,以供下一次使用。
- 使用Message.obtain()方式创建
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
六、关于ThreadLocal,你的理解是?
-
- ThreadLocal在Handler体系中用来保证线程中只有一个Looper对象,在调用Looper.prepare()方法时,会先从sThreadLocal.get()中获取,如果能够获取到,会抛出异常,获取不到才会实例化一个Looper对象,并通过ThreadLocal.set()方法保存起来。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
-
- ThreadLocal这种机制原理,并不是ThreadLocal自己实现的,而是通过它的内部类ThreadLocalMap来实现,它是一个Map,以ThreadLocal为Key,泛型(T)为值。这里就是Looper对象为值了。
public class ThreadLocal {
static class ThreadLocalMap {
static class Entry extends WeakReference> {
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}
}
-
- 而每个Thread线程中都会有一个ThreadLocal.ThreadLocalMap的变量,所以当调用ThreadLocal.get()方法时,都会去获取当前线程,并获取它的ThreadLocal.ThreadLocalMap对象,再从中以ThreadLocal为key来获取Looper
Thread.java
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal.java
public class ThreadLocal {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
七、为什么不能在子线程更新UI?
举个例子:TextView.setText() --> TextView.checkForRelayout() ---> View.requestLayout() ---> ViewRootImpl.requestLayout() ---> ViewRootImpl.checkThread()
每次都需要检测当前线程是否是主线程,主线程至于是哪里?什么时间设置的需要另外追踪?
ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
解惑:
1、为什么子线程不能操作UI
当一个线程第一次启动时,Android同时会启动一个对应的主线程,这个主线程就是UI线程(ActivityThread)。UI线程负责处理和UI相关的事件,如用户按键点击、屏幕触摸等。系统不会为每个组件都单独创建一个线程,在同一进程中UI组件都会在UI线程中实例化。系统对每个组件调用都是从UI线程分发出去的。所以响应系统回调的方法永远都是在UI线程里运行。如onKeyDown()的回调
- 那为什么选择一个主线程干这些活呢?换个说法,Android为什么使用单线程模型,它有什么好处?
现代GUI线程就是使用了单线程模型(采用一个专门的线程从队里中抽取事件,并把它们转发给应用程序定义的事件处理器)。单线程化不单单存在于Android中,Qt、XWindows都是单线程化。当然,也有人试图用多线程的GUI,最终由于竞争条件和死锁导致的稳定性问题等。又回到了单线程化的事件队列模型上来。单线程模型通过限制来达到线程安全。
- 在子线程中更新UI抛出异常的原因?(ViewRootImpl构造方法会初始化ViewRoot的mThread,更新Ui时会对比mThread和Thread.currentThread())
ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
......
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
- 非UI线程是可以刷新UI的,前提是它要拥有自己的ViewRootImpl。可以通过WindowManager.addView()来实现,在WindowManagerImpl.addView() ---> WindowManagerGlobal.addView()内部将会实例化ViewRootImpl
class NonUiThread extends Thread{
@Override
public void run() {
Looper.prepare();
TextView tx = new TextView(MainActivity.this);
tx.setText("non-UiThread update textview");
WindowManager windowManager = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
windowManager.addView(tx, params);
Looper.loop();
}
}
- 那么主线程什么时候创建的ViewRootImpl呢?本把主线程赋值的?实在onResume()的时候,对应到ActivityThread.handleResumeActivity()方法。
ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
...
ActivityClientRecord r = performResumeActivity(token, clearHide);
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
......
}
八、子线程中维护Looper在消息队列无消息的时候处理方案?
子线程中我们创建了Handler,并调用了Looper.prepare()和loop()。一旦调用loop()就开启了阻塞。如果消息队列无消息时,子线程仍然会阻塞,只有我们手动调用了子线程中Looper.quit()后才会解除阻塞,往后执行。还可以释放Message内存。
new Thread(){
public void run() {
Looper.prepare();
new Handler(){
...
}
Looper.loop();
}
}
-
- 看到loop()函数中只有msg==null的时候才会return,解除阻塞。
Looper.java
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
}
}
-
- 当消息队列无消息时,nativePollOnce()会阻塞等待下一个消息到来,如果调用了quit()就会唤醒return null。
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
if (mQuitting) {
dispose();
return null;
}
...
}
}
}
-
- 当我们手动调用子线程Looper.quit()时,它调用的是MessageQueue.quit()。首先,主线程的Looper不允许退出;其次,它会移除和释放MessageQueue中的message内存;另外注意到nativeWake它将通知唤醒MesageQueue.next()的阻塞,往后执行,返回null。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
...
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
九、什么事epoll机制
-
- Handler消息框架队列需要解决时间排序问题和阻塞问题。
-
- 事件不仅仅是应用层的东西,底层,驱动层都有事件,所以单单依靠java层面的队列如BlockingQueue无法解决
-
- 消息I/O有阻塞I/O和非阻塞I/O:阻塞I/O是一堆事件得一个一个挨个处理,这种不可忍。非阻塞I/O每个事件和事件处理器都类似有单独通道连接处理。
-
- Android系统事件和App事件处理类似C/S模型,C/S模型通信可以用Socket来解决,Socket采用的是select模型。
-
- Select模型(非阻塞I/O)可以处理非阻塞,但是它需要轮询来获取对应的事件处理器(这里对应哪个App来处理事件),另外它的事件需要拷贝。
-
-
epoll机制(异步I/O),Looper.prepare()--> new MessageQueue() ---> nativeInit()时调用底层的epoll_create()函数为每个App添加文件描述符,并将其添加到B+树(红黑树)中,但调用Looper.loop()开启死循环时,MessgeQueue.next()就会调用nativePollOnce()来阻塞,底层为epoll_wait()。当有事件(epoll_ctl)来时,事件携带了文件描述符,就可以通过红黑树快速定位哪个App处理事件。并将其加入缓冲队列中,等待一个一个处理分发。
-
十、一个线程有几个looper? 如何保证,又可以有几个Handler
一个线程只有一个Looper和一个messageQueue,通过ThreadLocal保证。可以有多个Handler.
十一、handler内存泄漏的原因,其他内部类为什么没有这个问题
-
- 根本原因:长生命周期对象持有短生命周期对象
-
- 内部类会持有外部类的引用,GCRoot链: MainActivity --> Handler ---> Message ---> MessageQueue ---> Looper ---> Thread
-
- 解决内存泄漏: 本质是断开GCRootl链,1:将Handler定义为static,它将不在持有外部类引用。 2:Activity onDestroy()时,Handler.removeCallbacksAndMessages移除所有的Message,因为Message持有Handler。
十二、为什么主线程可以new Handler 其他子线程可以吗 怎么做?
-
- Handler机制需要有四个要素:Looper,MessageQueue,Message,Handler。实例化Handler之间需要先实例化Looper,不然会抛出异常。它需要执行在prepare()和loop()之间
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
...
}
-
- 之前我们说过在ActivityThread的main方法中,系统已经帮我们调用了prepareMainLooper()实例化了主线程的Looper对象,并且调用了loop()。主线程中所有代码都执行在它们俩之间。
ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
...
Looper.loop();
}
-
- 子线程如果我们需要new Handler(),则需要先Looper.prepare()创建子线程Looper对象。并且如果子线程需要退出的话,需要手动调用Looper.quit()方法。
new Thread(){
public void run() {
Looper.prepare();
new Handler(){
...
}
Looper.loop();
}
}