2024字节跳动Android开发岗面试,关于Handler,面试官问了我这些……

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,那么就必须做三个操作:

  1. prepare();

  2. loop();

  3. 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)]

此外还有一系列的视频教程,大家如果需要也可以找我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值