之前总习惯于写笔记,总结都是写到笔记中,我的博客有点吃醋了,所以我决定也要宠幸下我的博客,把我平时总结的一些东西更新到博客中,如有不对之处,欢迎指出!
今天的回归之作是聊聊我们的老朋友--Handler。
背景:前几年去参加面试的时候,就开始问Handler了,一直都是停留在会用的层面上,那时候面试,只要能答上:Handler是用于解决子线程更新UI的问题;Handler发送消息,存入Message队列,后台有个Looper一直从Message队列去取,然后message处理事件并回调消息更新UI。OK,这样子基本就算过了。但是后面去翻翻源码,发现又有很多新的收获,那今天就一起来聊聊吧。
首先我们需要复习以及准备一些东西,大家都知道,我们平时写一些测试代码的时候,都需要一个主方法(main),这种规则到处都能看到,在Android中,我们启动一个页面的时候,入口也是一个标记为"android.intent.action.MAIN"的activity,然后我们想一下,应用启动之后是做了什么呢,其实应用启动后的事情由ActivityThread开始,这个ActivityThread就是我们平时说的主线程,按照刚才说的原则,我们在ActivityThread类中发现了这个方法
public static void main(String[] args) { //方法内容有删减
……
Looper.prepareMainLooper();//初始化当前进程的Looper对象
……
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
……
Looper.loop();//调用Looper的loop方法开启,这里面是一个for无限循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
是不是感到无比温馨,那我们看一下Looper.prepareMainLooper();做了什么
public static void prepareMainLooper() {
prepare(false);//创建,实质上就是new一个Looper对象,仔细看看下面prepare方法中的new的地方
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//从sThreadLocal中取出Looper并赋值给sMainLooper
}
}
public static @Nullable Looper myLooper() {//代码在类中别的位置,拷过来方便看
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//如果sThreadLocal已经和Looper绑定,则抛出异常(目的是确保一个线程中只能执行一次Looper.prepare();
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//将new出的Looper设置到(主)线程的本地变量中,与当前变量进行了绑定
}
原来做了这么多事情,总结来说就是启动了一个主线程,主线程中默认new了一个Looper对象,并和主线程中的变量sThreadLocal进行绑定,并且这个线程只能有一个Looper对象,如果我们在我们的MainActivity中执行Looper.prepare();方法的话,直接就崩了,异常提示就是上面的prepare方法中的异常信息。既然Looper是new出来的,然后我们扒一扒Looper构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
看到了吧,是创建了一个MessageQueue对象,真的是好基友啊,有福同享,要生一起生。那么这个MessageQueue就厉害了,既然一个线程只存在一个Looper,那么,也就只new了一个MessageQueue,这样的话,后续我们用handler发消息的话,都是被送到这个MessageQueue中。
大家刚才在ActivtiyThread的main方法中有看到:Looper.loop();这个方法吧,Looper做的事情就是不断地从MessageQueue中取出Message,然后处理指定的任务,怎样不断地获取呢,我们平时写一个程序,没有指定具体的结束时机的,是不是要把执行的程序放到一个死循环中去执行,那么这里的loop方法,就是执行一个死循环,而主线程就靠着这个死循环一直运行着,从而保证app进程能够持续地运行。(这里是不是可以联想一下我们整个手机系统,从开机开始,就是开启了一个死循环然后一直运行,哇,那如果一直不关机,就得一直运行,都没有休息,好可怜!)
好了,到这里呢,我们的背景知识就准备好了,现在我们开始来去扒一扒Handler~
使用handler最终还是从new 一个Handler对象开始,看看源码:
public Handler(@Nullable Callback callback, boolean async) {
……
mLooper = Looper.myLooper();//准备looper对象
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//准备messagequeue对象
mCallback = callback; //这里要注意,如果是sendMessage的方式,需要用到这个变量
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//就是主线程初始化绑定的那个looper
}
在主线程new一个handler的时候,就把前面new好的looper和messagequeue准备好了,(子线程也是可以创建handler对象的,但是需要执行Looper.prepare()方法,不然这里的mLooper就为null,会报异常),然后经过一顿操作,不管是通过什么方式,最终都是走 sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);这个方法,这里的SystemClock.uptimeMillis() + delayMillis 是系统时间加上我们设置的延迟的时间相加,在message插入队列的时候会用到。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//获取handler绑定的消息队列,这里的mQueue就是上面 mLooper.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);
}
总结来说,就是handler调用sendMessage方法时,首先会获取到当前的handler绑定的messagequeue (mQueue),而handler发出的消息会将该handler实列对象赋值给该message的target属性,然后handler中最终会调用enqueueMessage方法,该方法的功能就是向消息队列插入这个handler 发送的消息
enqueueMessage方法就是核心处理方法了,
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //这里还是在handler类中,把当前Handler实例对象作为msg的target属性
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//调用MessageQueue 的enqueueMessage方法
}
这里是MessageQueue的enquueMessage方法:
boolean enqueueMessage(Message msg, long when) {
……
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);//调用nativ方法来唤醒
}
}
return true;
}
这里就是把发送的消息插入到队列中的合适位置,最后通过needWake判断是否需要调用底层nativeWake唤醒整个消息队列
handler发送消息的诸多方法中有一个需要分析下:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在Message中有一个callback变量,而post的发送方式,将runnable赋值给message的callback,然后执行dispatchMessage方法的时候,分两种情况:一、msg的callback 不为空,就是通过post方法发送消息的,会直接执行runnable中的run方法,实质上就是一个回调接口。二、msg中的callback为空,则是通过sendmessage发送消息的,直接调用handler的handlemessage方法。看一下handler中的dispatchMessage方法,
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) { //post 的方式,传入了runnable
handleCallback(msg);
} else {
if (mCallback != null) { //这个在上面谈到handler的构造函数的时候有说到
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();//runnable实际上是一个回调接口,和线程没关系
}
public void handleMessage(@NonNull Message msg) {
}
至此handler的执行流程就结束了,然后我们继续探究几个比较重要的点:
1、之前我们都是说UI更新必须在主线程(UI线程)中操作,其实也不一定,这里是指通常情况下,当然也有特殊场景,SurfaceView在非UI线程中也是可以更新的,而且有时候在某些特定的场景下,子线程会趁主线程不注意,偷偷更新了UI,这里是指一些简单的view的更新。解释说明一下,这个场景是之在oncreate方法中创建子线程更新UI,因为在onresume执行之前,viewroot还没创建,不会有checkThread的检查机制,所以线程执行在onresume之前的话,那些简单的更新UI操作会被保留生效。
2、Looper.loop()是个死循环,为什么不会卡死UI?这个就涉及到一些native的知识了,看一下MessageQueue中的next()方法:
@UnsupportedAppUsage
Message next() { //内容有删减
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);//该函数涉及到linux中的pipe和epoll机制,这里就不过多扩展了
synchronized (this) { //说明messagequeue是线程安全的
// 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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
}
}
}
当message的队列为空时,就会阻塞在message.next 的nativePollOnce()方法里,执行nativePollOnce方法时,主线程会释放CPU资源进入休眠,然后等到下一条消息到达时,这里采用的是epoll机制,往pipe管道写入数据来唤醒主线程。然后从上面的代码也可以看出messagequeue是线程安全的,因为使用了synchronized (this) 同步锁。
3、前面提到我们也可以在子线程中维护looper,使用完之后记得调用Looper.quitSafely()的方法释放掉,这里最终会调用messagequeue的removeAllFutureMessagesLocked()方法,在该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,如果大于当前时间就会removeAllMessagesLocked()方法,也就是回收全部消息,反之,则回收部分消息,同时没有被回收的消息任然可以被取出执行。
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);
}
}
}
4、handler怎么引起内存泄漏的?我们使用handler的时候通过new的方式,这里handler是一个内部类,默认持有activity的引用,然后前面谈到handler发送的message对象的target属性设置的就是handler,如此,message就持有handler的引用,当我们调用postDelay()执行演示任务,如果还没到我们设置的delay时间,只要app一直在运行,messagequeue就一直存在,其message对象一直持有handler的引用。这样一来当我们要销毁该activity时,GC回收内存的时候会从GcRoot检测所有内存,发现MessageQueue -> Message -> Handler -> Activity 引用链,那么只要message没有被销毁,activity的引用就一直存在,这样会间接导致activity无法给回收,造成内存泄露。
5、至于如何解决handler造成的内存泄漏问题,有这几种方案:一、静态内部类 + 弱引用,将 Handler 声明为静态内部类,Handler 也就不再持有 Activity 的引用,所以 Activity 可以随便被回收,但是没有了引用之后,handler就不能操作 Activity 中对象,所以可以在 Handler 中添加一个对 Activity 的弱引用( WeakReference );二、activity销毁的时候,调用handler的removeCallbacksAndMessages()方法清空消息队列,这样就可以释放activity从而进行回收。
那么今天handler的内容大致聊到这里,欢迎大佬指正或者补充哈!
本文主要探讨了Android中的Handler机制,从应用启动的ActivityThread开始,解析Looper.prepareMainLooper()和Looper.loop()的作用,以及主线程与Looper、MessageQueue的关系。文章详细阐述了Handler创建、发送消息的过程,包括MessageQueue如何插入消息以及如何通过nativeWake唤醒消息队列。此外,还讨论了Handler可能导致的内存泄漏问题及其解决方案,以及UI线程更新的特殊情况。
357

被折叠的 条评论
为什么被折叠?



