android handler面试,Android面试里常见的Handler相关问题

一、Handler、MessageQueue、Looper 的关系

模型职责

Handler: 负责向MQ里入队消息(sendMessage)、删除消息(removeMessage)、处理消息(handleMessage)

MessageQueue: 负责投递消息(enqueueMessage),取消息(next)

Looper: 负责轮询MQ,将取出的消息分发给对应的 handler 处理(loop)

Message: 单链表结构(next),绑定目标Handler(target),记录发送时间(when),发送内容(obj)

调用关系

Handler: 需要指定 Looper,Looper 中会有对应的 MQ

MessageQueue: 对应一个待处理的 Message 链表

Looper: 对应一个 MQ

Message: 对应处理自己的 Handler

Handler 消息队列机制的使用过程如下

```

class HandlerThread extends Thread {

private Handler mHandler;

public void run() {

Looper.prepare();

mHandler = new Handler(Looper.myLooper());

Looper.loop();

}

public Handler getThreadHandler() {

return mHandler;

}

}

class ActivityThread {

public static void main() {

//...

HandlerThread ht = new HandlerThread();

ht.start();

ht.getThreadHandler().post(

()-> {

//do xxx

}

);

}

}

```

关系确立时机

Handler Looper

class Handler {

public Handler(Looper looper, Callback callback, boolean async) {

mLooper = looper;

mQueue = looper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

}

初始化 Handler 的时候,会指定 Looper,同时会将 Looper 中的 MessageQueue 也绑定到Handler上

Handler Message

class Handler {

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

}

在发送消息的时候,Handler 会和 Message 发生绑定,msg.target=this,可以理解为谁发的消息就要谁去处理

Looper Thread

class Looper {

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));

}

}

sThreadLocal.set(new Looper(quitAllowed)),借助 ThreadLocal 将 Looper 和 Thread 进行绑定,所以一个 Thread 只会对应一个 Looper,是 ThreadLocal 决定的,想知道ThreadLocal原理的同学可以自己查查相关资料。

二、有关消息队列的一些问题

如何保障消息队列中的消息的时间顺序?

延时消息如何实现的?

消息的分发过程?

消息屏障是什么?

解决这些问题的关键在于MQ入队和出队消息的逻辑

首先,解决前两个问题,如何保障消息队列中的消息的时间顺序?延时消息如何实现的?

这需要分析enqueueMessage()

boolean enqueueMessage(Message msg, long when)

这个方法是在Handler#sendMessageAtTime()中调用的,when 是SystemClock.uptimeMillis() + delayMillis的结果,即系统正常运行时间(不算阻塞、休眠)+消息延时时长

去除特殊情况,和加锁逻辑后的代码如下

boolean enqueueMessage(Message msg, long when) {

msg.when = when;

Message p = mMessages;//之前的消息链

if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.

msg.next = p; //新 msg 作为链表头

mMessages = msg; //更新消息链

needWake = mBlocked;

} else {

Message prev;

for (;;) {

prev = p;

p = p.next;

if (p == null || when < p.when) { //走到链表尾部或找到正确时间线位置

break;

}

}

msg.next = p; // invariant: p == prev.next

prev.next = msg; //将msg插入到消息链

}

}

具体插入逻辑简化为,向队列头部插入消息,比如调用了handler#sendMessageAtFrontOfQueue,按时间线插入消息,如下图所示,假设系统当前运行时间定格在+500的状态,开始向队列插入消息的状况

39478976c902

enqueue.jpg

特殊说明: 在+4100插入了一个延时500的消息,在入队的时候需要调整链表位置,所以+4100的消息会插入到+4200的后面,保证了消息的时间顺序,同时巧妙的实现了延时消息。

然后,分析一下取出正常消息(同步消息)的过程,精简代码如下

Message next() {

int pendingIdleHandlerCount = -1; // -1 only during first iteration

int nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(ptr, nextPollTimeoutMillis); //阻塞 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) {

if (now < msg.when) { //没到发消息的时间,队列需要阻塞

// Next message is not ready. Set a timeout to wake up when it is ready.

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); //此时会给 nextPollTimeoutMillis 赋值,在循环开始时会阻塞

} else {

// Got a message.

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else { //调整消息链表

mMessages = msg.next;

}

msg.next = null; //断开后面的消息链

if (DEBUG) Log.v(TAG, "Returning message: " + msg);

msg.markInUse(); //标记为消息已使用

return msg;// 返回得到的消息

}

} else {

// No more messages.

nextPollTimeoutMillis = -1;

}

if (pendingIdleHandlerCount <= 0) {

// No idle handlers to run. Loop and wait some more.

mBlocked = true;

continue;

}

}

}

正常从消息队列取消息的过程可以理解为如下过程

39478976c902

dequeue.jpg

假设系统时间为+4300,此时取出next为链表中的最后一个消息,when=+4500,此时因为还未到执行消息的时候,就会给nextPollTimeoutMillis赋值,然后会检查pendingIdleHandlerCount,此时一定没有IdleHandler要处理任务,进入下一次循环,进入阻塞状态

第三个问题,消息的分发过程?

在上面刚刚分析了取消息的过程 MessageQueue#next(),现在看看是谁调用了 next 并且得到了当前的 Message 对象,这个问题答案在Looper#loop()中,简化逻辑后的代码如下

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

msg.target.dispatchMessage(msg);

}

}

逻辑很简单,就是死循环调用next(),如果得到msg,就调用 msg.target.dispatchMessage 即回到 Handler#dispatchMessage

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

逻辑也很简单,就是指定了一个回调的优先级,先看 msg 是否指定了 Callback,如果没有就查看是否给 Handler 设置了 Callback,如果也没有就执行 Handler#handleMessage,这个方法需要自己重写

第四,消息屏障是什么?

简单来说,消息屏障就是个标志,标志开启时会优先处理异步消息

这个问题也需要在消息的入队出队时寻找答案,由于应用开发的时候使用场景比较少,所以放到最后解答,现在看一下有关消息屏障的源码

Message next() {

Message prevMsg = null;

Message msg = mMessages;

if (msg != null && msg.target == null) {

do {// Stalled by a barrier. Find the next asynchronous message in the queue.

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

//后面走上面分析过的流程,但此时的msg已经是一个异步消息了

}

判断有无消息屏障的条件是 msg.target == null,在有消息屏障下,会不断循环链表找到异步消息,条件为!msg.isAsynchronous(),现在再来看看如何设置消息屏障MessageQueue#postSyncBarrier

public int postSyncBarrier() {

return postSyncBarrier(SystemClock.uptimeMillis());

}

private int postSyncBarrier(long when) {

// Enqueue a new sync barrier token.

// We don't need to wake the queue because the purpose of a barrier is to stall it.

synchronized (this) {

final int token = mNextBarrierToken++;

final Message msg = Message.obtain();

msg.markInUse();

msg.when = when;

msg.arg1 = token;

//此处没设置target,所以target是null

Message prev = null; //找到合适的位置插入屏障消息

Message p = mMessages;

if (when != 0) {

while (p != null && p.when <= when) {

prev = p;

p = p.next;

}

}

if (prev != null) { // invariant: p == prev.next

msg.next = p;

prev.next = msg;

} else {

msg.next = p;

mMessages = msg;

}

return token;

}

}

这个方法就是在指定时间处插入一个屏障消息,如果是0,就是在消息队列最头部插入,然后消息队列在取消息的时候发现有消息屏障就会向后遍历直到找到异步消息(msg.isAsynchronous()),将消息分发处理。

个人理解:

消息屏障的作用其实并不是名字定义的同步异步的意思,而是给消息定了优先级,给异步消息开了后门(只要有屏障消息在,之后读取的消息都是异步消息)。

三、小结

消息队列中的消息按时间顺序排序和延时消息的实现,都是依靠系统时间的推移实现的

如果下一个消息的时间超过系统当前时间,则会阻塞

屏障消息的判断条件是 msg.target == null,正常使用Handler发的消息是无法将 target 置为 null 的,需要手动调用postSyncBarrier

补充:

IdleHandler的作用是什么?

IdleHandler 可以用来提升提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(譬如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android面试问题可以包括以下几个方面: 1. Android基础知识:这包括对Android操作系统的理解,四大组件(活动、服务、内容提供者、广播接收器)的使用和生命周期等等。 2. Java编程:Android开发主要使用Java语言,因此对Java语法、OOP(面向对象编程)的理解和Java常用类的使用都是必备的。 3. 布局和界面:Android应用开发离不开界面的设计和布局,对常用布局(如线性布局、相对布局、帧布局)和控件的使用熟练掌握是重要的。 4. 数据存储和管理:了解Android中数据的存储方式,如使用SQLite数据库、SharedPreferences等进行本地数据存储,也要了解网络数据的请求和处理。 5. 多线程和异步编程:对于Android应用来说,处理耗时任务需使用多线程或异步编程方式,了解如何使用AsyncTask、Handler等组件来实现多线程操作是必要的。 6. 性能优化和内存管理:在开发Android应用时,需要对应用的性能进行优化,理解内存泄漏的原因,掌握使用Android Profiler等工具进行性能分析和调优。 7. 第三方库和框架:熟悉常用的Android第三方库和框架(如Retrofit、Glide、ButterKnife),了解它们的用途和基本使用方法。 8. 安全性:了解Android应用开发中的安全性问题,如如何保护用户数据、防止恶意攻击等。 9. 版本控制工具:熟悉使用Git等版本控制工具进行代码管理和团队协作。 10. 实际项目经验:面试官可能会问到你在之前的实际项目中遇到的问题和解决方案,以及你的贡献和角色等。 回答这些问题时,建议结合自己的实际经验和项目经历进行回答,展示自己的能力和对Android开发的理解。 ### 回答2: 在Google的Android面试中,可能会遇到以下问题: 1. 请介绍一下Android平台的架构。 Android平台的架构由Linux内核、硬件驱动、运行时库、应用框架和应用组成。Linux内核是底层硬件控制的核心,硬件驱动提供了与硬件交互的接口,运行时库包括了核心库和C/C++库,应用框架提供了开发应用程序的API接口,应用则是用户使用的应用程序。 2. 请介绍一下Android的四大组件。 Android的四大组件包括Activity、Service、Broadcast Receiver和Content Provider。Activity负责提供用户界面,Service负责后台执行长时间运行的任务,Broadcast Receiver负责接收和处理系统广播消息,Content Provider负责数据共享和存取。 3. 请介绍一下Activity的生命周期。 Activity的生命周期包括onCreate、onStart、onResume、onPause、onStop、onDestroy等方法。当Activity被创建时会调用onCreate方法,当其可见但还无法与用户交互时会调用onStart和onResume方法,当其失去焦点但未被销毁时会调用onPause方法,当其完全不可见时会调用onStop方法,当Activity被销毁时会调用onDestroy方法。 4. 请介绍一下Android的布局文件和代码文件。 Android的布局文件使用XML语言编写,用于定义应用界面的组件和布局方式。代码文件用于处理布局文件中的组件的行为,包括点击事件、数据绑定等逻辑。 5. 请介绍一下Android的Intent。 Intent是Android中用于传递消息和执行动作的对象。它可以用于启动Activity、Service和Broadcast Receiver,也可以用于发送和接收数据。通过Intent,可以在不同的组件之间传递数据和通信。 总结: Android面试中,会涉及到Android平台的架构、四大组件、Activity的生命周期、布局文件和代码文件的概念以及Intent的使用。准备这些内容,能够更好地回答面试官的问题,展示自己的Android开发能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值