Handler
从 sendMessage 到 handleMessage
跟着我的节奏 按下 Ctrl,鼠标点击 Handler,开始快乐! Skr!!
先找到我们最常用的方法 sendMessage(message)
它直接调用了 sendMessageDelayed(message, delayMillis)
然而 sendMessageDelayed 也只是做了一点校验操作(针对delayMillis的非负校验)
然后 它 直接调用了 sendMessageAtTime(message,SystemClock.uptimeMillis() + delayMillis);
sendMessageAtTime:重头戏开始了
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
可见,由该方法开始,MessageQueue(消息队列)出现了
创建出消息队列之后,把消息和延迟时长都传进去。
Handler中的 enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;// Message 注入 handler
if (mAsynchronous) {
// {true:异步;false:同步}
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue是一个根据时间排序的 优先级队列
MessageQueue中的 enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
// 校验 handler
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) {
//根据 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;
}
MessageQueue 时间优先级队列 新消息插入图:
从sendMessage到MessageQueue的路线打通了,接下来看看 Looper 这条线。
sendMessage是往消息队列里加新消息,Looper是循环消耗(处理)消息。
Looper的loop()方法主要做两件事:(首先声明,Looper 是一个死循环)
-
把消息从消息队列中取出来。
Message msg = queue.next(); // might block ( next()可能阻塞 )
MessageQueue 的 next()方法,就是取出下一个消息(message)
-
调用Handler的dispatchMessage(msg)的方法去执行message
msg.target.dispatchMessage(msg);
Handler的 dispatchMessage 方法:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg);//就是我们重写的方法。 } }
最后调用 handleMessage(msg)方法:就此形成闭环。 大赞!!!
问题追寻
Looper是在那初始化的呢?
Looper的初始化:
首先看Looper的构造方法:(居然是私有的,说明不是其他类启动的Looper)
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
Looper有自己的启动类方法:prepare(true)
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { //如果发现对应的threadLocal(key) 已经 有对应的Looper(value)了,抛出异常。 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
Looper 有了,那动力(thread)何来呢?
让我们继续探寻!(点进ThreadLocal.set()方法)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);// 获得当前线程的 threadLocalMap(用来保存线程上下文的) if (map != null) map.set(this, value);// 将此 threadLocal 与 looper 对象绑定到上述 map 中去。 else createMap(t, value); }
getMap方法就是返回对应线程的 threadLocals 成员变量。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
这样就完成了绑定。
一个线程 对应 一个 ThreadLocalMap,里面的存放着ThreadLocal(key):Looper(value)一对映射。
Handler会造成内存泄露的问题,为什么呢?怎么解决呢?
当我们使用Handler时,一般都会采用匿名内部类的方式来重写 handleMessage() 方法。
就是这个匿名内部类的使用导致了内存泄露。
那么其他内部类会导致内存泄露吗?为什么?
这个问题说到后面自然明了。
先说Handler的内部类为什么会导致内存泄露。
-
从Java语法的层面出发,非静态(static)内部类会 持有 一个外部类的对象(this)。
正式这样,内部类才可以直接访问外部类的成员属性。
-
由Handler的执行机制我们可以发现 message 会 和 handler 进行绑定,随后进入到MessageQueue中去。
-
而MessageQueue中的Message有可能是延迟消息(delay),message可能会存活相当一段时间。
-
而在message存活的这一段时间内,handler持有的 this 也不能被释放,尽管这个页面已经执行了销毁。
-
从GC的回收机制来看,有着强引用的对象是不会轻易回收,JVM宁愿抛出异常。
由此可见,单纯的内部类是不会造成内存泄露的,是因为Handler独特的运行机制,才导致了这个问题。
那我们该如何解决呢?
- 从Java语法的角度触发,非静态内部类会持有外部类对象,而静态外部类却不会,我们把Handler对象搞成静态的就可以了。
- 在分析中,我们提到了强引用不好回收,相应的,我们可以利用弱引用(WeakReference),这样必要的时候,JVM是可以回收 外部类对象的(当我们要用外部类对象时,还可以去ReferenceQueue中拿)。
为什么我们在主线程中 new Handler 就可以直接用?并没有看到跟Looper的关系。Why?
当我们在 Activity 中使用 Handler 时。
final Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
从始至终我们都没关心 Looper 在哪准备的,就这样用了,能用就说明一定有地方准备了。
我们知道Android是一个基于事件触发的框架。
他要想做点什么事,总得先来点刺激。
那么刺激APP开始运行的事件:应该就是我们点击桌面上的APP图标了。
这个过程到底发生了什么。
明确几点:
- Android主界面(桌面)也是一个APP,通常将其称作 launcher
- Zygote是Android系统创建新进程的核心进程,负责启动Dalvik虚拟机,加载一些必要的系统资源和系统类,启动system_server进程,随后进入等待处理app应用请求
- Application是虚拟机(Dalvik/Art)的一个实例。每个进程只有一个虚拟机,每个进程只有一个Application实例。单进程App只有一个Application实例,多进程App有多个Application实例
- 每个程序都有自己的程序入口,APP的程序入口是 ActivityThread 类里 main 方法。
我废这么多话其实就想引出一点,APP的程序入口在 ActivityThread 里,这里面一定有秘密。
ActivityThread 类 的main 方法局部:
public static void main(String[] args) {
//...被忽略的代码段...
Looper.prepareMainLooper();
//...被忽略的代码段...
if (sMainThreadHandler == null) {
// 获得主线程的 handler
sMainThreadHandler = thread.getHandler();
}
//...被忽略的代码段...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可见,在APP的入口处,我们找到了 Looper 的 初始化。
Looper.prepareMainLooper:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);// 创建 Looper 对象,并与主线程绑定
synchronized (Looper.class) {
if (sMainLooper != null) {
//同样,针对主线程的 Looper 也只准有一个
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
就此我们发现,其实这一切在冥冥之中该有的都有。
并且我们顺藤摸瓜 发现了 H 类,(按住Ctrl点进了 getHandler方法)
class H extends Handler
这个 H 继承了 Handler 类,并重写了很多方法,这说明 针对主线程的 Handler 还是有很大区别的。
H类:handleMessage方法局部:
细看之后发现不得了,这里面处理的情况似乎都是非常重要的组件动作。
- 尤其这个 退出APP(EXIT_APPLICATION)的操作,居然就是 quit 掉了主线程 的Looper。
居然可以操作 APP 的退出,这说明 Android 的处理机制很可能就是 Looper + Handler 模式。
如果我们在子线程里搭建了Handler机制,该怎么结束他呢?
why:(不然一直死循环,线程永远不会结束,资源永远不会释放)
我们知道 Looper 的 loop() 方法就是个死循环,当 MessageQueue 中没有消息时,就会阻塞。
Message msg = queue.next(); // might block
MessageQueue.next() 循环内局部代码:
for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //当 nextPollTimeoutMillis 为 -1 时,此方法将从 native 层持续阻塞,直到有事件发生 //如果 nextPollTimeoutMillis 为 0 时,则无需等待,直接返回。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //...被忽略的代码段... if (msg != null) { //...被忽略的代码段... } else { // No more messages. 当没有消息时 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. // 处理完所有挂起的消息后,立即处理退出消息。 if (mQuitting) { // 在调用 quit() dispose(); return null;//返回一个 null ,让loop()跳出循环。 } //...被忽略的代码段... }
Looper.loop() 循环内局部代码:
for (;;) { Message msg = queue.next(); // might block if (msg == null) { //当 next 方法 返回一个 null 时,跳出循环。 // No message indicates that the message queue is quitting. return; } //...被忽略的代码段... }
由上述代码我们看出,要想让loop()跳出循环,需要两步:
唤醒线程,让其继续执行
nativePollOnce很可能已经让线程阻塞了,需要调用nativeWake将其唤醒。
让 mQuitting = true,这样loop() 这边会 进入return 分支。
当调用 quit 时,MessageQueue.quit():
void quit(boolean safe) { if (!mQuitAllowed) { //主线程不允许自主 quit throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } //将其设置为 true ,为了next() 方法 返回一个 null,进而让 loop 跳出循环 mQuitting = true; //将 Message对象 的空间都置空。(Message的创建和维护采用了 享元设计模式) if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr);//将线程唤醒 } }
就此,loop跳出循环,此handler动作就此结束!