public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
//to Something
}
}
}
静态
//定义成static的,因为静态内部类不会持有外部类的引用
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {//to something}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
面试官:为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
小王: 每一个handler必须要对应一个looper,主线程会自动创建Looper对象,不需要我们手动创建,所以主线程可以直接创建handler。
在new handler的时候没有传入指定的looper就会默认绑定当前创建handler的线程的looper,如果没有looper就报错。
因为在主线程中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护Looper对象,所以需要我们自己手动维护。 所以要在子线程开启Handler要先创建Looper,并开启Looper循环
如果在子线程中创建了一个Handler,那么就必须做三个操作:
prepare();
loop();
quit();
面试官:子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
小王: 在Handler机制里面有一个Looper,在Looper机制里面有一个函数,叫做quitSafely()和quit()函数,这两个函数是调用的MessageQueue的quit()。
/**
- Quits the looper.
-
- Causes the {@link #loop} method to terminate without processing any
- more messages in the message queue.
-
- Any attempt to post messages to the queue after the looper is asked to quit will fail.
- For example, the {@link Handler#sendMessage(Message)} method will return false.
-
- Using this method may be unsafe because some messages may not be delivered
- before the looper terminates. Consider using {@link #quitSafely} instead to ensure
- that all pending work is completed in an orderly manner.
- @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
- Quits the looper safely.
-
- Causes the {@link #loop} method to terminate as soon as all remaining messages
- in the message queue that are already due to be delivered have been handled.
- However pending delayed messages with due times in the future will not be
- delivered before the loop terminates.
-
- Any attempt to post messages to the queue after the looper is asked to quit will fail.
- For example, the {@link Handler#sendMessage(Message)} method will return 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()函数。
// We can assume mPtr != 0 because mQuitting was previously false.
这个函数的调用,就会叫醒等待的地方,醒来之后,就接着往下执行。
//native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行
//阻塞操作,等待nextPollTimeoutMillis时长
nativePollOnce(ptr, nextPollTimeoutMillis);
往下执行后,发现 Message msg = mMessages; 是空的,然后就执行了这个,就接着往下走。
if (msg != null) {
…
} else {
// No more messages.
//没有消息,nextPollTimeoutMillis复位
nextPollTimeoutMillis = -1;
}
然后又调用了这个方法,并且return了null。
// Process the quit message now that all pending messages have been handled.
//如果消息队列正在处于退出状态返回null,调用dispose();释放该消息队列
if (mQuitting) {
dispose();
return null;
}
所以说,这个时候Looper就结束了(跳出了死循环),则达成了第二个作用:释放线程。
面试官:既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?
小王: 这里主要关注 MessageQueue 的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。
消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。
面试官:我们使用 Message 时应该如何创建它?
小王: 创建的它的方式有两种: 一种是直接 new 一个 Message 对象, 另一种是通过调用 Message.obtain() 的方式去复用一个已经被回收的 Message, 当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。
Message 中的 sPool 就是用来存放被回收的 Message,当我们调用 obtain 后,会先查看是否有可复用的对象,如果真的没有才会去创建一个新的 Message 对象。
补充:主要的 Message 回收时机是: 1.在 MQ 中 remove Message 后; 2.单次 loop 结束后; 3.我们主动调用 Message 的 recycle 方法后
面试官:Looper死循环为什么不会导致应用卡死?
小王: Launch桌面的图标第一次启动Activity时,会最终走到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法,下面是loop方法循环。
主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。
阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。
总结:应用卡死压根与这个Looper没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是ANR,而Looper是睡眠。
最后
本文以问答的方式列举了一些在整个面试中,Android岗位被问到的一些关于Handler的问题。(里面还要部分是我在准备面试前在牛客刷到的,还有几个其他的Android朋友被问到的题)
写太多的话估计大家最多也只是收藏箱积灰,就先放这些吧。
由于文章篇幅的原因,很多问题都只是粗略的回答了一些关键的信息,如果有朋友需要更回答的更好,更详细一点的话可以,想要理解的更深一点的话可以来我的GitHub或者**【点这里】免费领取!**
我已经把Handler相关知识点整理成了一份PDF文档,里面附有各个知识点详细的答案,有的是朋友分享的,还有的是我在机构那里购买的,不过,现在都免费分享给大家了。
此外还有一系列的视频教程,大家如果需要也可以找我。
一些关键的信息,如果有朋友需要更回答的更好,更详细一点的话可以,想要理解的更深一点的话可以来我的GitHub或者**【点这里】免费领取!**
我已经把Handler相关知识点整理成了一份PDF文档,里面附有各个知识点详细的答案,有的是朋友分享的,还有的是我在机构那里购买的,不过,现在都免费分享给大家了。
[外链图片转存中…(img-y4d8Errt-1736532262521)]
此外还有一系列的视频教程,大家如果需要也可以找我。